#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") // Prevent Windows headers from defining `min`/`max` macros which break C++ std::min/std::max #ifndef NOMINMAX #define NOMINMAX #endif #include #include "resource.h" #include "ui.h" #include "process_watch.h" #include "wintoast_handler.h" #include #include #include #include "utils.h" #include "localization.h" #include "eyetracking_runtime.h" #include "user_data_manager.h" #include "model_download_manager.h" UI ui; G_DPI g_Dpi; Utils utils; UI_Run_State rs; bool windowClosed; /// Styles int PushSubTextStyle() { ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); return 1; } int PushTextInputStyle() { ImGui::PushStyleColor(ImGuiCol_FrameBg, UIConstants.textbox_fill); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, UIConstants.textbox_active); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, UIConstants.textbox_active); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.textbox_text); return 4; } int PushTextInputHintStyle() { ImGui::PushStyleColor(ImGuiCol_FrameBg, UIConstants.textbox_fill); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, UIConstants.textbox_active); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, UIConstants.textbox_active); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.textbox_text); ImGui::PushStyleColor(ImGuiCol_TextDisabled, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); return 5; } int PushPopupStyle() { ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIConstants.popup_title_color); return 1; } int PushProgressBarStyle() { ImGui::PushStyleColor(ImGuiCol_FrameBg, UIConstants.progress_fill); return 1; } int PushSliderStyle() { ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, g_Dpi.F(UIConstants.control_thickness))); style.FramePadding.y = g_Dpi.F(UIConstants.control_thickness); ImGui::PushStyleColor(ImGuiCol_FrameBg, UIConstants.slider_fill); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, UIConstants.slider_active); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, UIConstants.slider_active); ImGui::PushStyleColor(ImGuiCol_SliderGrab, UIConstants.slider_button); ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, UIConstants.slider_button_active); return 5; } int PushButtonStyleGrey() { ImGui::PushStyleColor(ImGuiCol_Button, UIConstants.grey_button_fill); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIConstants.grey_button_hover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIConstants.grey_button_click); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.grey_button_text); return 4; } int PushButtonStyle() { ImGui::PushStyleColor(ImGuiCol_Button, UIConstants.button_fill); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIConstants.button_hover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIConstants.button_click); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.button_text); return 4; } int PushButtonStyleInactive() { ImGui::PushStyleColor(ImGuiCol_Button, UIConstants.button_fill_inactive); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIConstants.button_hover_inactive); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIConstants.button_click_inactive); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.button_text_inactive); return 4; } int PushButtonBoxStyle() { ImGui::PushStyleColor(ImGuiCol_Button, UIConstants.checkbox_fill); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIConstants.checkbox_hover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIConstants.checkbox_click); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.checkbox_icon); return 4; } int PushButtonCategoryStyle() { ImGui::PushStyleColor(ImGuiCol_Button, UIConstants.checkbox_fill * ImVec4(0.9f, 0.9f, 0.9f, 1)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIConstants.checkbox_hover * ImVec4(0.9f, 0.9f, 0.9f, 1)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIConstants.checkbox_click); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.checkbox_icon); return 4; } int PushButtonCategoryStyleHighlighted() { ImGui::PushStyleColor(ImGuiCol_Button, UIConstants.checkbox_fill * ImVec4(1.3f, 1.3f, 1.3f, 1)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIConstants.checkbox_hover * ImVec4(1.3f, 1.3f, 1.3f, 1)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIConstants.checkbox_click); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.checkbox_icon); return 4; } int PushCheckboxStyle() { ImGui::PushStyleColor(ImGuiCol_FrameBg, UIConstants.checkbox_fill); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, UIConstants.checkbox_hover); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, UIConstants.checkbox_click); ImGui::PushStyleColor(ImGuiCol_CheckMark, UIConstants.checkbox_icon); return 4; } int PushDropdownStyle() { ImGui::PushStyleColor(ImGuiCol_FrameBg, UIConstants.dropdown_fill); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, UIConstants.dropdown_hover); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, UIConstants.dropdown_click); ImGui::PushStyleColor(ImGuiCol_Button, UIConstants.dropdown_button); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIConstants.dropdown_button_hover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIConstants.dropdown_button_click); ImGui::PushStyleColor(ImGuiCol_PopupBg, UIConstants.dropdown_fill); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, UIConstants.dropdown_listing_hover); ImGui::PushStyleColor(ImGuiCol_HeaderActive, UIConstants.dropdown_button_hover); return 9; } int PushDropdownStyleInactive() { ImGui::PushStyleColor(ImGuiCol_FrameBg, UIConstants.dropdown_fill_inactive); ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, UIConstants.dropdown_hover_inactive); ImGui::PushStyleColor(ImGuiCol_FrameBgActive, UIConstants.dropdown_click); ImGui::PushStyleColor(ImGuiCol_Button, UIConstants.dropdown_button); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIConstants.dropdown_button_hover); ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIConstants.dropdown_button_click); ImGui::PushStyleColor(ImGuiCol_PopupBg, UIConstants.dropdown_fill); ImGui::PushStyleColor(ImGuiCol_HeaderHovered, UIConstants.dropdown_hover_inactive); ImGui::PushStyleColor(ImGuiCol_HeaderActive, UIConstants.dropdown_click); return 9; } void centered(float width) { auto windowWidth = ImGui::GetWindowSize().x; ImGui::SetCursorPosX((windowWidth - width) * 0.5f); } void centered_text(const std::string& text) { auto windowWidth = ImGui::GetWindowSize().x; auto textWidth = ImGui::CalcTextSize(text.c_str()).x; ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f); ImGui::Text(text.c_str()); } void centered_textwrapped(const std::string& text) { auto windowWidth = ImGui::GetWindowSize().x; auto textWidth = ImGui::CalcTextSize(text.c_str(), nullptr, false, windowWidth).x; ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f); ImGui::TextWrapped(text.c_str()); } bool centered_button(const std::string& text, ImVec2 size = g_Dpi.Vec2(0, 0)) { auto windowWidth = ImGui::GetWindowSize().x; float buttonWidth = size.x > 0 ? size.x : ImGui::CalcTextSize(text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2.0f; ImGui::SetCursorPosX((windowWidth - buttonWidth) * 0.5f); return ImGui::Button(text.c_str(), size); } void show_tooltip(const char* msg) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, g_Dpi.Vec2(10, 10)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, g_Dpi.F(5)); float maxWidth = ImGui::GetWindowSize().x - g_Dpi.F(150); ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + maxWidth); ImGui::Text(msg); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); ImGui::PopStyleVar(2); } bool slider_with_overdrive(const char* label, ImS32* p_data, const ImS32* p_min, const ImS32* p_max, const ImS32* p_split, float* p_split_pos, const char* format, ImGuiSliderFlags flags) { const ImVec4 Overdrive_FrameBg(0.423f, 0.156f, 0.258f, 1); const ImVec4 Overdrive_FrameBgHovered(0.380f, 0.235f, 0.392f, 1); const ImVec4 Under_FrameBg(0, 0, 0.7f, 0.40f); const ImVec4 Overdrive_SliderGrab(UIConstants.slider_button + ImVec4(0.1f, 0.1f, 0.1f, 0)); const ImVec4 Overdrive_SliderGrabActive(UIConstants.slider_button_active + ImVec4(0.1f, 0.1f, 0.1f, 0)); ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) return false; ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); const float w = ImGui::CalcItemWidth(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, g_Dpi.F(6.0f))); const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f)); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f)); const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0; ImGui::ItemSize(total_bb, style.FramePadding.y); if (!ImGui::ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0)) return false; // Default format string when passing NULL if (format == NULL) format = ImGui::DataTypeGetInfo(ImGuiDataType_S32)->PrintFmt; const bool hovered = ImGui::ItemHoverable(frame_bb, id, ImGuiItemFlags_None); bool temp_input_is_active = temp_input_allowed && ImGui::TempInputIsActive(id); if (!temp_input_is_active) { const bool clicked = hovered && ImGui::IsMouseClicked(0, ImGuiInputFlags_None, false); const bool make_active = (clicked || g.NavActivateId == id); if (make_active && clicked) ImGui::SetKeyOwner(ImGuiKey_MouseLeft, id); if (make_active && temp_input_allowed) if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))) temp_input_is_active = true; if (make_active && !temp_input_is_active) { ImGui::SetActiveID(id, window); ImGui::SetFocusID(id, window); ImGui::FocusWindow(window); g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); } } if (temp_input_is_active) { // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set const bool is_clamp_input = (flags & ImGuiSliderFlags_AlwaysClamp) != 0; return ImGui::TempInputScalar(frame_bb, id, label, ImGuiDataType_S32, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL); } // Draw frame const ImU32 frame_col = ImGui::ColorConvertFloat4ToU32(g.ActiveId == id ? UIConstants.slider_active : hovered ? UIConstants.slider_active : UIConstants.slider_fill); ImGui::RenderNavHighlight(frame_bb, id); //ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding); // // Calculate the split in the frame. Where does the standard frame end, and the overdrive frame begin? const float grab_padding = 2.0f; // FIXME: Should be part of style. float grab_sz = style.GrabMinSize; const float slider_usable_pos_min = frame_bb.Min[ImGuiAxis_X] + grab_padding + grab_sz * 0.5f; const float slider_usable_pos_max = frame_bb.Max[ImGuiAxis_X] - grab_padding - grab_sz * 0.5f; float split_point = ((float)(*p_split) - (float)(*p_min)) / ((float)(*p_max) - (float)(*p_min)); //float split_point = OverdriveRatioFromValue(ImGuiDataType_S32, p_split, p_min, p_max); split_point = ImLerp(slider_usable_pos_min, slider_usable_pos_max, split_point) - grab_sz / 2; *p_split_pos = split_point; // Return to sender ImRect std_box(frame_bb.Min, ImVec2(frame_bb.Min.x + split_point + 5, frame_bb.Max.y)); ImRect over_box(ImVec2(frame_bb.Min.x + split_point, frame_bb.Min.y), frame_bb.Max); ImGui::RenderFrame(std_box.Min, std_box.Max, frame_col, true, g.Style.FrameRounding); const ImU32 over_frame_col = ImGui::ColorConvertFloat4ToU32(g.ActiveId == id ? Overdrive_FrameBgHovered : hovered ? Overdrive_FrameBgHovered : Overdrive_FrameBg); ImGui::RenderFrame(over_box.Min, over_box.Max, over_frame_col, true, g.Style.FrameRounding); // Render the lower bound if min is less than 0 if (*p_min < 0) { float under_point = ((float)(0) - (float)(*p_min)) / ((float)(*p_max) - (float)(*p_min)); ImRect under_box(frame_bb.Min, ImVec2(frame_bb.Min.x + (under_point * frame_bb.Max.x) + (grab_sz / 2), frame_bb.Max.y)); const ImU32 under_frame_col = ImGui::ColorConvertFloat4ToU32(Under_FrameBg); ImGui::RenderFrame(under_box.Min, under_box.Max, under_frame_col, true, g.Style.FrameRounding); } // Slider behavior ImRect grab_bb; const bool value_changed = ImGui::SliderBehavior(frame_bb, id, ImGuiDataType_S32, p_data, p_min, p_max, format, flags, &grab_bb); if (value_changed) ImGui::MarkItemEdited(id); // Render grab if (grab_bb.Max.x > grab_bb.Min.x) if (*p_data > *p_split) { window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, ImGui::ColorConvertFloat4ToU32(g.ActiveId == id ? Overdrive_SliderGrabActive : Overdrive_SliderGrab), style.GrabRounding); } else { window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, ImGui::ColorConvertFloat4ToU32(g.ActiveId == id ? UIConstants.slider_button_active : UIConstants.slider_button), style.GrabRounding); } ImGui::PopStyleVar(); // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; const char* value_buf_end = value_buf + ImGui::DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), ImGuiDataType_S32, p_data, format); if (g.LogEnabled) ImGui::LogSetNextTextDecoration("{", "}"); ImGui::RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f)); if (label_size.x > 0.0f) ImGui::RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0)); return value_changed; } /// Custom message loop to handle systray icon menu choices. LRESULT custom_wndproc(int32_t code, WPARAM wParam, LPARAM lParam) { if (code < 0) { return 0; } const LPCWPRETSTRUCT lpcwprs = (LPCWPRETSTRUCT)lParam; switch (lpcwprs->message) { case WM_SYSTRAY_MENU: switch (LOWORD(lpcwprs->lParam)) { case WM_LBUTTONUP: [[fallthrough]]; case WM_RBUTTONUP: uint32_t style = GetWindowLong(lpcwprs->hwnd, GWL_STYLE); HMENU hPop = CreatePopupMenu(); InsertMenu(hPop, 0, MF_BYPOSITION | MF_STRING, 2001, L"Exit..."); if (style & WS_VISIBLE) { InsertMenu(hPop, 0, MF_BYPOSITION | MF_STRING, 2002, L"Hide Utility..."); } else { InsertMenu(hPop, 0, MF_BYPOSITION | MF_STRING, 2002, L"Open Utility..."); } POINT pt; GetCursorPos(&pt); SetForegroundWindow(lpcwprs->hwnd); WORD cmd = TrackPopupMenu(hPop, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, 0, lpcwprs->hwnd, NULL); SendMessage(lpcwprs->hwnd, WM_COMMAND, cmd, 0); DestroyMenu(hPop); break; } break; // WM_SYSTRAY_MENU case WM_COMMAND: switch (LOWORD(lpcwprs->wParam)) { case 2001: ui.alive.store(false); break; case 2002: uint32_t style = GetWindowLong(lpcwprs->hwnd, GWL_STYLE); if (style & WS_VISIBLE) { ShowWindow(lpcwprs->hwnd, SW_HIDE); windowClosed = true; } else { ShowWindow(lpcwprs->hwnd, SW_SHOW); windowClosed = false; } break; } } return 0; } /// void window_close_callback(GLFWwindow* window) { // When the 'X' is clicked to close the window, we just want to minimize to systray. ShowWindow(glfwGetWin32Window(window), SW_HIDE); windowClosed = true; } // Helper UI functions that don't need to be a member of the UI struct static inline bool refresh_rate_button(const char* label, bool show_highlighted, bool is_enabled, const ImVec2& size = g_Dpi.Vec2(0, 0)) { bool retval; int pushed_styles; if (show_highlighted) { ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.96f, 0.71f, 0.18f, 0.65f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.96f, 0.71f, 0.18f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.93f, 0.70f, 0.07f, 1.00f)); ImGui::PushItemFlag(ImGuiItemFlags_None, true); } else { pushed_styles = PushButtonStyleInactive(); } if (!is_enabled) { ImGui::BeginDisabled(true); } retval = ImGui::Button(label, size); if (!is_enabled) { ImGui::EndDisabled(); } if (show_highlighted) { ImGui::PopItemFlag(); ImGui::PopStyleColor(3); } else { ImGui::PopStyleColor(pushed_styles); } if (!is_enabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(LSTR(StringID::TOOLTIP_STEAMVR_FOLDER_REQUIRED).c_str()); } return retval; } bool enable_72hz_option() { return false; } std::pair get_et_status_string() { // Status bool running = eyetracking_runtime.RuntimeActive(); bool has_critical_error = eyetracking_runtime.LoopRunning() && rs.et_enabled && (eyetracking_runtime.FrustumLoadFailure() || eyetracking_runtime.ModelLoadFailure() || eyetracking_runtime.CameraAccessRevoked()); bool searching = !steamvrWatch.BlockFromSteamVRAbsent() && eyetracking_runtime.CameraSearching() && !eyetracking_runtime.CameraAccessRevoked(); bool steamVRWait = eyetracking_runtime.LoopRunning() && eyetracking_runtime.SteamVRWaiting(); ImVec4 c = (searching || steamVRWait) ? ImVec4(1, 0.1f, 1, 1) : has_critical_error ? ImVec4(1, 0, 0, 1) : running ? ImVec4(0, 1, 0, 1) : ImVec4(1, 0, 0, 1); std::string s = LSTR(StringID::STATUS_LOADING); if (searching || steamVRWait) { int dotCount = (int)(ImGui::GetTime() * 2) % 4; std::string dots = std::string(dotCount, '.'); const std::string label = searching ? std::format("{}{{}}", LSTR(StringID::EYETRACKING_STATUS_SEARCHING_CAMERAS)) : (rs.et_selected_apps.size() > 0 ? std::format("{}{{}}", LSTR(StringID::EYETRACKING_STATUS_WAITING_VR_APP)) : std::format("{}{{}}", LSTR(StringID::EYETRACKING_STATUS_WAITING_STEAMVR))); s = std::vformat(label, std::make_format_args(dots)); } else { s = has_critical_error ? LSTR(StringID::EYETRACKING_STATUS_ERROR) : running ? LSTR(StringID::EYETRACKING_STATUS_ACTIVE) : LSTR(StringID::EYETRACKING_STATUS_NOT_RUNNING); } return { s, c }; } std::string format_hours_minutes(int64_t minutes) { // -1 is used to mark when the data has not been received yet if (minutes != -1) { if (minutes < 60) { const std::string minuteString = LSTR(StringID::LABEL_MINUTES); return std::format("{} {}", minutes, minuteString); } else { int64_t myhours = minutes / 60; int64_t myminutes = minutes % 60; const std::string hourString = myhours == 1 ? LSTR(StringID::LABEL_HOUR) : LSTR(StringID::LABEL_HOURS); const std::string minuteString = myminutes == 1 ? LSTR(StringID::LABEL_MINUTE) : LSTR(StringID::LABEL_MINUTES); if (minutes != 0) { return std::format("{} {}, {} {}", myhours, hourString, myminutes, minuteString); } else { return std::format("{} {}", myhours, hourString); } } } else { return LSTR(StringID::STATUS_LOADING); } } bool UI::close_existing_process() { HWND hwnd = FindWindow(NULL, L"Bigscreen Beyond Utility"); if (hwnd) { SendMessage(hwnd, WM_COMMAND, 2001, 0); return true; } return false; } bool UI::bring_foreground(bool foreground_external = false) { HWND hwnd = foreground_external ? FindWindow(NULL, L"Bigscreen Beyond Utility") : glfwGetWin32Window(window); if (hwnd) { // If the window is minimized, restore it if (IsIconic(hwnd)) { ShowWindow(hwnd, SW_RESTORE); } else { ShowWindow(hwnd, SW_SHOW); } SetForegroundWindow(hwnd); return true; } return false; } UI_Run_State* UI::get_runtime_state() { return &rs; } void UI::localize_tables() { auto with_default_value = [](StringID id, const std::string& value) { std::string text = LSTR(id); size_t idx = text.find("%d"); if (idx != std::string::npos) { text.replace(idx, 2, value); } return text; }; warned_settings = { {SteamVR_Warned_Settings::MotionSmoothing, {LSTR(StringID::SETTING_MOTION_TITLE), LSTR(StringID::SETTING_MOTION_DESC), "steamvr", "motionSmoothing", "true", false, true}}, {SteamVR_Warned_Settings::SuperSampleValue, {LSTR(StringID::SETTING_SAMPLE_VALUE_TITLE), LSTR(StringID::SETTING_SAMPLE_VALUE_DESC), "steamvr", "supersampleScale", "1", true}}, {SteamVR_Warned_Settings::SuperSampleAuto, {LSTR(StringID::SETTING_SAMPLE_AUTO_TITLE), LSTR(StringID::SETTING_SAMPLE_AUTO_DESC), "steamvr", "supersampleManualOverride", "false", false, true}}, {SteamVR_Warned_Settings::DisplayIdle, { LSTR(StringID::SETTING_DISPLAY_IDLE_TITLE), LSTR(StringID::SETTING_DISPLAY_IDLE_DESC), "power", "turnOffScreensTimeout", "60", false, true}}, {SteamVR_Warned_Settings::PauseVR, { LSTR(StringID::SETTING_PAUSE_TITLE), LSTR(StringID::SETTING_PAUSE_DESC), "power", "pauseCompositorOnStandby", "true", false, true}}, {SteamVR_Warned_Settings::GPUProfiling, { LSTR(StringID::SETTING_GPU_PROFILE_TITLE), LSTR(StringID::SETTING_GPU_PROFILE_DESC), "perfcheck", "GPUProfiling", "true"}}, {SteamVR_Warned_Settings::GPUBusMonitoring, { LSTR(StringID::SETTING_GPU_BUS_TITLE), LSTR(StringID::SETTING_GPU_BUS_DESC), "perfcheck", "gpuBusMonitoring", "true"}}, }; warning_banners = { {Warning_State::SteamVR, {LSTR(StringID::WARN_STEAMVR_SETTINGS), LSTR(StringID::WARN_VIEW_BUTTON)}}, {Warning_State::PartialUpdate, {LSTR(StringID::WARN_BAD_UPDATE), LSTR(StringID::WARN_VIEW_BUTTON)}}, {Warning_State::NoCamera, {LSTR(StringID::WARN_NO_CAMERA), LSTR(StringID::WARN_RESOLVE_BUTTON)}}, {Warning_State::UsingCPU, {LSTR(StringID::EYETRACKING_WARNING_GPU_UNAVAILABLE), ""}}, {Warning_State::CameraRevoked, {LSTR(StringID::EYETRACKING_WARNING_CAMERA_ACCESS_FAILED), ""}}, {Warning_State::ModelFailure, {LSTR(StringID::EYETRACKING_WARNING_MODEL_LOAD_FAILED), ""}}, {Warning_State::NoFrustum, {LSTR(StringID::EYETRACKING_WARNING_EYETRACKING_DATA_LOAD_FAILED), ""}}, }; DFR_apps = { { LSTR(StringID::EYETRACKING_APP_DCS), LSTR(StringID::EYETRACKING_APP_DCS_SETUP) }, { LSTR(StringID::EYETRACKING_APP_PAVLOV), LSTR(StringID::EYETRACKING_APP_PAVLOV_SETUP) }, { LSTR(StringID::EYETRACKING_APP_IRACING) + "*", LSTR(StringID::EYETRACKING_APP_IRACING_SETUP) }, { LSTR(StringID::EYETRACKING_APP_MSFS_2024) + "*", std::format("{}\n{}", LSTR(StringID::EYETRACKING_APP_MSFS_2024_SETUP_PREFIX), LSTR(StringID::EYETRACKING_APP_MSFS_2024_SETUP)) }, { LSTR(StringID::EYETRACKING_APP_KAYAK_VR_MIRAGE), LSTR(StringID::EYETRACKING_APP_KAYAK_SETUP) } }; accuracy_tips = { LSTR(StringID::EYETRACKING_TIP_WEAR_CONFIGURATIONS), LSTR(StringID::EYETRACKING_TIP_CLEAN_CAMERA_LENSES), LSTR(StringID::EYETRACKING_TIP_OLD_MODEL_REENROLL), LSTR(StringID::EYETRACKING_TIP_IPD_CHANGE_REENROLL), LSTR(StringID::EYETRACKING_TIP_ALIGNMENT_HELPER_USAGE) }; subscribers = { { LSTR(StringID::EYETRACKING_RECIPIENT_VRCHAT_OSC), "##osc", LSTR(StringID::EYETRACKING_RECIPIENT_VRCHAT_OSC_DESC), false, [&]() { return rs.et_publish_vrchat; }, [&](bool v) { rs.et_publish_vrchat = v; utils.write_settings_file(rs); } }, { LSTR(StringID::EYETRACKING_RECIPIENT_VRCFT), "##cft", LSTR(StringID::EYETRACKING_RECIPIENT_VRCFT_DESC), false, [&]() { return rs.et_publish_vrcft; }, [&](bool v) { rs.et_publish_vrcft = v; utils.write_settings_file(rs); } }, { LSTR(StringID::EYETRACKING_RECIPIENT_DFR), "##xr", LSTR(StringID::EYETRACKING_RECIPIENT_DFR_DESC), true, nullptr, [&](bool) { rs.current_page = Page_Type::DFRHome; } }, { LSTR(StringID::EYETRACKING_RECIPIENT_EYE_VIEWER), "##view", LSTR(StringID::EYETRACKING_RECIPIENT_EYE_VIEWER_DESC), false, [&]() { return rs.et_publish_viewer; }, [&](bool v) { if (v) { rs.pending_popup = "Eye Viewer Warning"; } else { rs.et_publish_viewer = v; } } } }; et_adv_setting_toggles = { { LSTR(StringID::EYETRACKING_REQUIRE_STEAMVR_PRESENCE_DESC), { { LSTR(StringID::EYETRACKING_REQUIRE_STEAMVR_PRESENCE), [&]() { return rs.et_require_steamvr_presence; }, [&](bool v) { rs.et_require_steamvr_presence = v; utils.write_settings_file(rs); } } } }, { LSTR(StringID::EYETRACKING_SMOOTHING_ENABLED_DESC), { { LSTR(StringID::EYETRACKING_SMOOTHING_ENABLED), [&]() { return rs.et_smoothing_enabled; }, [&](bool v) { rs.et_smoothing_enabled = v; utils.write_settings_file(rs); } } } }, { LSTR(StringID::EYETRACKING_TRACK_EYES_DESC), { { LSTR(StringID::EYETRACKING_TRACK_RIGHT_EYE), [&]() { return rs.et_right_eye_enabled; }, [&](bool v) { if (!v) { rs.et_left_eye_enabled = true; } rs.et_right_eye_enabled = v; utils.write_settings_file(rs); } }, { LSTR(StringID::EYETRACKING_TRACK_LEFT_EYE), [&]() { return rs.et_left_eye_enabled; }, [&](bool v) { if (!v) { rs.et_right_eye_enabled = true; } rs.et_left_eye_enabled = v; utils.write_settings_file(rs); } } } } }; et_adv_setting_dropdowns = { { LSTR(StringID::EYETRACKING_BLINK_LABEL), LSTR(StringID::EYETRACKING_BLINK_DESC), std::vector {LSTR(StringID::EYETRACKING_BLINK_Option1), LSTR(StringID::EYETRACKING_BLINK_Option2), LSTR(StringID::EYETRACKING_BLINK_Option3)}, [&]() { int mode = rs.et_blink_mode; if (mode < 0 || mode >= 3) { mode = 0; } return mode; }, [&](int v) { try { int parsed = std::clamp(v, 0, 2); if (rs.et_blink_mode == parsed) { return; } rs.et_blink_mode = parsed; utils.write_settings_file(rs); } catch (...) { } } }, }; et_adv_setting_inputs = { { LSTR(StringID::EYETRACKING_SMOOTHING_INTENSITY), with_default_value(StringID::EYETRACKING_SMOOTHING_INTENSITY_DESC, utils.float_to_string(UIConstants.filter_default, 2)), 45, [&]() { return utils.float_to_string(rs.et_smoothing_intensity, 3); }, [&](const std::string& v) { try { float parsed = std::stof(v); parsed = std::clamp(parsed, 0.0f, 1.0f); if (rs.et_smoothing_intensity == parsed) return; rs.et_smoothing_intensity = parsed; utils.write_settings_file(rs); } catch (...) { } } }, { LSTR(StringID::EYETRACKING_OSC_SEND_IP_ADDRESS), LSTR(StringID::EYETRACKING_OSC_SEND_IP_ADDRESS_DESC), 100, [&]() { return rs.et_osc_ip; }, [&](const std::string& v) { rs.et_osc_ip = v.data(); utils.write_settings_file(rs); } }, { LSTR(StringID::EYETRACKING_OSC_SEND_PORT), LSTR(StringID::EYETRACKING_OSC_SEND_PORT_DESC), 60, [&]() { return std::to_string(rs.et_osc_port); }, [&](const std::string& v) { try { int parsed = std::stoi(v); if (rs.et_osc_port == parsed) return; rs.et_osc_port = parsed; utils.write_settings_file(rs); } catch (...) { } } } }; dfr_setting_inputs = { { LSTR(StringID::EYETRACKING_PERIPHERAL_CLARITY), with_default_value(StringID::EYETRACKING_PERIPHERAL_CLARITY_DESC, UIConstants.peripheral_default), 45, [&]() { return rs.quad_peripheral_multiplier; }, [&](const std::string& v) { try { float parsed = std::stof(v); parsed = std::clamp(parsed, 0.0f, 1.0f); std::string normalized = utils.float_to_string(parsed, 3); if (rs.quad_peripheral_multiplier == normalized) return; rs.quad_peripheral_multiplier = normalized; } catch (...) {} } }, { LSTR(StringID::EYETRACKING_FOCUS_CLARITY), with_default_value(StringID::EYETRACKING_FOCUS_CLARITY_DESC, UIConstants.focus_default), 45, [&]() { return rs.quad_focus_multiplier; }, [&](const std::string& v) { try { float parsed = std::stof(v); parsed = std::clamp(parsed, 0.0f, 5.0f); std::string normalized = utils.float_to_string(parsed, 3); if (rs.quad_focus_multiplier == normalized) return; rs.quad_focus_multiplier = normalized; } catch (...) {} } }, { LSTR(StringID::EYETRACKING_HORIZONTAL_FOCUS_REGION_SCALE), with_default_value(StringID::EYETRACKING_HORIZONTAL_FOCUS_REGION_SCALE_DESC, UIConstants.horizontal_focus_default), 45, [&]() { return rs.quad_horizontal_focus_section; }, [&](const std::string& v) { try { float parsed = std::stof(v); parsed = std::clamp(parsed, 0.0f, 1.0f); std::string normalized = utils.float_to_string(parsed, 3); if (rs.quad_horizontal_focus_section == normalized) return; rs.quad_horizontal_focus_section = normalized; } catch (...) {} } }, { LSTR(StringID::EYETRACKING_VERTICAL_FOCUS_REGION_SCALE), with_default_value(StringID::EYETRACKING_VERTICAL_FOCUS_REGION_SCALE_DESC, UIConstants.vertical_focus_default), 45, [&]() { return rs.quad_vertical_focus_section; }, [&](const std::string& v) { try { float parsed = std::stof(v); parsed = std::clamp(parsed, 0.0f, 1.0f); std::string normalized = utils.float_to_string(parsed, 3); if (rs.quad_vertical_focus_section == normalized) return; rs.quad_vertical_focus_section = normalized; } catch (...) {} } }, { LSTR(StringID::EYETRACKING_EDGE_SMOOTHING), with_default_value(StringID::EYETRACKING_EDGE_SMOOTHING_DESC, UIConstants.transition_thickness_default), 45, [&]() { return rs.quad_transition_thickness; }, [&](const std::string& v) { try { float parsed = std::stof(v); parsed = std::clamp(parsed, 0.0f, 0.5f); std::string normalized = utils.float_to_string(parsed, 3); if (rs.quad_transition_thickness == normalized) return; rs.quad_transition_thickness = normalized; } catch (...) {} } } }; et_model_filters = { { LSTR(StringID::EYETRACKING_MODEL_FILTER_CLOUD), Models_Filter::RemoteModels }, { LSTR(StringID::EYETRACKING_MODEL_FILTER_DOWNLOADED), Models_Filter::OfflineModels }, { LSTR(StringID::EYETRACKING_MODEL_FILTER_ALL), Models_Filter::All }, }; et_app_list_tabs = { { LSTR(StringID::EYETRACKING_APPS_LIST_RUNNING).c_str(), ET_AppsSection::Running }, { LSTR(StringID::EYETRACKING_APPS_LIST_SELECTED).c_str(), ET_AppsSection::Selected }, }; } void UI::load_fonts() { // Load font ImGuiIO& io = ImGui::GetIO(); Lang_Setting setting_en = languages.at(Locales::En); Lang_Setting setting_jp = languages.at(Locales::Jp); ImFontConfig mergeConfig; mergeConfig.MergeMode = true; mergeConfig.PixelSnapH = true; mergeConfig.GlyphMinAdvanceX = 0.0f; static const ImWchar japaneseRanges[] = { 0x0020, 0x00FF, // Basic Latin 0x3000, 0x30FF, // Hiragana + Katakana 0x4E00, 0x9FAF, // Kanji common range 0xFF01, 0xFF1E, // Fullwidth parenthesis, colon 0x2026, 0x2026, // Ellipsis 0 }; // Null terminator if (std::filesystem::exists(setting_en.font_path) && std::filesystem::exists(setting_jp.font_path)) { io.Fonts->ClearFonts(); io.FontDefault = nullptr; font = io.Fonts->AddFontFromFileTTF(setting_en.font_path.c_str(), g_Dpi.F(setting_en.font_size)); io.Fonts->AddFontFromFileTTF(setting_jp.font_path.c_str(), g_Dpi.F(setting_jp.font_size), &mergeConfig, japaneseRanges); font_header = io.Fonts->AddFontFromFileTTF(setting_en.font_path.c_str(), g_Dpi.F(setting_en.header_size)); io.Fonts->AddFontFromFileTTF(setting_jp.font_path.c_str(), g_Dpi.F(setting_jp.header_size), &mergeConfig, japaneseRanges); if (font) { io.FontDefault = font; } } else { io.FontGlobalScale = g_Dpi.scale; } } ImVec2 UI::update_dpi() { float xscale, yscale; glfwGetWindowContentScale(window, &xscale, &yscale); xscale = std::clamp(xscale, 0.0f, 2.0f); yscale = std::clamp(yscale, 0.0f, 2.0f); g_Dpi.Set(xscale); return ImVec2(xscale, yscale); } /// void UI::create_window(bool startClosed) { toastHandler.create_instance(); if (glfwInit() == GLFW_FALSE) { alive.store(false); return; } // Create window. glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); window = glfwCreateWindow(window_width, window_height, "Bigscreen Beyond Utility", NULL, NULL); if (!window) { alive.store(false); return; } if (startClosed) { window_close_callback(window); } // Create systray icon. NOTIFYICONDATA nid = { sizeof(nid) }; nid.hWnd = glfwGetWin32Window(window); nid.uFlags = NIF_ICON | NIF_TIP | NIF_SHOWTIP | NIF_MESSAGE; nid.uCallbackMessage = WM_SYSTRAY_MENU; nid.uID = 1; LoadIconMetric(NULL, L"images/icon.ico", LIM_SMALL, &nid.hIcon); LoadString(NULL, IDS_TOOLTIP, nid.szTip, ARRAYSIZE(nid.szTip)); nid.uVersion = NOTIFYICON_VERSION_4; Shell_NotifyIcon(NIM_ADD, &nid); Shell_NotifyIcon(NIM_SETVERSION, &nid); // Intercept messages before GLFW handles them. HHOOK hook = SetWindowsHookExA(WH_CALLWNDPROCRET, custom_wndproc, GetModuleHandle(0), GetCurrentThreadId()); // Intercept close calls to just minimize to systray. glfwSetWindowCloseCallback(window, window_close_callback); // Center window on screen. int max_width = GetSystemMetrics(SM_CXSCREEN); int max_height = GetSystemMetrics(SM_CYSCREEN); ImVec2 window_scale = update_dpi(); glfwSetWindowMonitor(window, NULL, (max_width / 2) - (window_width / 2), (max_height / 2) - (window_height / 2), window_width * window_scale.x, window_height * window_scale.y, GLFW_DONT_CARE); // Setup framebuffer. glfwMakeContextCurrent(window); glfwSwapInterval(1); int w, h; glfwGetFramebufferSize(window, &w, &h); glViewport(0, 0, w, h); // Setup GUI. ImGui::CreateContext(); ImGuiIO& gui_io = ImGui::GetIO(); ImGui::StyleColorsDark(); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 130"); // Load fonts load_fonts(); // Style scaling ImGuiStyle& style = ImGui::GetStyle(); style.ScaleAllSizes(window_scale.x); // Load interface icons tex_loader.LoadPng("images/logo.png"); tex_loader.LoadPng("images/edit.png"); tex_loader.LoadPng("images/delete.png"); for (const auto& [locale, setting] : languages) { tex_loader.LoadPng(setting.flag_path); } } #pragma region Rendered Pages void UI::show_home_page(UI_Run_State& rs) { // Bridge driver install prompt if (!rs.installed_bridge_driver && !rs.shown_bridge_driver_notice && rs.checked_steamvr_settings && rs.valid_path) { rs.shown_bridge_driver_notice = true; rs.pending_popup = "Beyond SteamVR Add-On"; } // GUI items. bool loading_something = hid_handler.startup_camera_restart.load() || changing_refresh.load() || saving.load() || changing_ipd.load() || uploading_lighthouse_config.load() || hid_handler.entering_dfu.load(); if (loading_something) { ImGui::BeginDisabled(); } ImGui::PushItemWidth((float)(g_Dpi.F(window_width) - 4 * ImGui::GetStyle().FramePadding.x)); // ********** Refresh rate selection ********** if (show_refresh_rate_buttons(rs)) { utils.update_refresh_rate(rs); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); // ********** IPD ********** auto style = ImGui::GetStyle(); float window_edge = (float)g_Dpi.F(window_width) - g_Dpi.F(2) * style.FramePadding.x; ImVec2 control_vect = ImGui::GetCursorPos(); control_vect.x += g_Dpi.F(UIConstants.control_padding); if (changing_ipd.load()) { ImGui::SetCursorPosY(control_vect.y + g_Dpi.F(UIConstants.control_thickness)); ImGui::Text(LSTR(StringID::STATUS_CHANGING_IPD).c_str()); ImGui::SetCursorPos(control_vect); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, g_Dpi.F(UIConstants.control_thickness))); pushed_styles = PushProgressBarStyle(); ImGui::ProgressBar((float)ipd_progress.load() / 100.0f, ImVec2(window_edge - control_vect.x, 0)); ImGui::PopStyleColor(pushed_styles); ImGui::PopStyleVar(); } else { ImGui::SetCursorPosY(control_vect.y + g_Dpi.F(UIConstants.control_thickness)); ImGui::Text(LSTR(StringID::LABEL_IPD_ADJUSTMENT).c_str()); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(3)); ImGui::SetCursorPosX(ImGui::GetCursorScreenPos().x - g_Dpi.F(5)); ImGui::Text("(?)"); ImGui::PopStyleColor(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(LSTR(StringID::TOOLTIP_IPD_ADJUSTMENT).c_str()); } if (!rs.ipd_supported || !rs.valid_path) { ImGui::BeginDisabled(true); } ImGui::SameLine(); pushed_styles = PushSliderStyle(); ImGui::SetCursorPos(control_vect); ImGui::PushItemWidth(window_edge - control_vect.x); ImGui::SliderInt("##ipd", &rs.adjusted_ipd, UIConstants.ipd_min, UIConstants.ipd_max, !rs.ipd_supported ? "" : "%dmm", ImGuiSliderFlags_NoInput); if (ImGui::IsItemDeactivatedAfterEdit()) { rs.pending_popup = "IPD Change"; } ImGui::PopItemWidth(); ImGui::PopStyleColor(pushed_styles); ImGui::PopStyleVar(); if (!rs.ipd_supported || !rs.valid_path) { ImGui::EndDisabled(); } if (!rs.valid_path && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(LSTR(StringID::TOOLTIP_STEAMVR_FOLDER_REQUIRED).c_str()); } } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); // ********** Fan speed ********** control_vect = ImGui::GetCursorPos(); control_vect.x += g_Dpi.F(UIConstants.control_padding); if (rs.overdrive || finding_fan_gen.load()) { ImGui::BeginDisabled(); } ImGui::SetCursorPosY(control_vect.y + g_Dpi.F(UIConstants.control_thickness)); ImGui::Text(LSTR(StringID::LABEL_FAN_SPEED).c_str()); if (rs.overdrive && Localization::GetLanguage() == Locales::En) { ImGui::SameLine(); ImGui::Text("\t(Overdrive)"); } ImGui::SameLine(); pushed_styles = PushSliderStyle(); ImGui::SetCursorPos(control_vect); ImGui::PushItemWidth(window_edge - control_vect.x); if (ImGui::SliderInt("##fan_speed", &rs.fan_speed, UIConstants.fan_speed_min, UIConstants.fan_speed_max, "%d", ImGuiSliderFlags_NoInput)) { int fan_gen = utils.get_fan_generation(hid_handler.hmd_serial, rs); hid_handler.command_fan_speed_immediate(rs.fan_speed / (fan_gen == 2 ? 2 : 1)); } ImGui::PopItemWidth(); ImGui::PopStyleColor(pushed_styles); ImGui::PopStyleVar(); if (rs.overdrive || finding_fan_gen.load()) { ImGui::EndDisabled(); } if (finding_fan_gen.load() && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(LSTR(StringID::TOOLTIP_GETTING_INFO).c_str()); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); // ********** Display brightness ********** // table to show the normal vs. overdrive range of the slider if (finding_fan_gen.load()) { ImGui::BeginDisabled(); } if (ImGui::BeginTable("##overdrivelabel", 2, ImGuiTableFlags_BordersInnerV)) { ImGui::TableSetupColumn("##first", ImGuiTableColumnFlags_WidthFixed, rs.overdrive_split_pos - ImGui::GetStyle().FramePadding.x); ImGui::TableSetupColumn("##second", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text(LSTR(StringID::LABEL_DISPLAY_BRIGHTNESS).c_str()); ImGui::TableNextColumn(); ImGui::Text("\t%s", LSTR(StringID::LABEL_OVERDRIVE).c_str()); ImGui::EndTable(); } ImS32 osplit = 100; if (slider_with_overdrive("##brightness", &rs.slider_brightness, &UIConstants.brightness_min, &UIConstants.overdrive_brightness_max, &osplit, &rs.overdrive_split_pos, "%d", ImGuiSliderFlags_NoInput)) { if (rs.slider_brightness > 100) { rs.overdrive_brightness = rs.slider_brightness; rs.overdrive = true; rs.fan_speed = 100; uint16_t converted = (uint16_t)((((float)(rs.slider_brightness - 100) * 0.01f) * 553) + 266); hid_handler.command_oled_brightness(converted); int fan_gen = utils.get_fan_generation(hid_handler.hmd_serial, rs); hid_handler.command_fan_speed_immediate(rs.fan_speed / (fan_gen == 2 ? 2 : 1)); } else { rs.brightness = rs.slider_brightness; rs.overdrive = false; uint16_t converted = (uint16_t)((((float)(rs.slider_brightness) * 0.01f) * 216) + 50); hid_handler.command_oled_brightness(converted); } } if (finding_fan_gen.load()) { ImGui::EndDisabled(); } if (finding_fan_gen.load() && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(LSTR(StringID::TOOLTIP_GETTING_INFO).c_str()); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); // ********** Table with RGB LED control and the stats panel / save button ********** if (ImGui::BeginTable("##ledandstats", 2, ImGuiTableFlags_BordersInnerV)) { ImGui::TableSetupColumn("##rgbled", ImGuiTableColumnFlags_WidthFixed, g_Dpi.F(250) + 4 * ImGui::GetStyle().FramePadding.x); ImGui::TableSetupColumn("##stats", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); ImGui::Text(LSTR(StringID::LABEL_LED_COLOR).c_str()); pushed_styles = PushTextInputStyle(); if (ImGui::ColorPicker3("##mycolor", rs.color, ImGuiColorEditFlags_PickerHueWheel | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHex | ImGuiColorEditFlags_NoSidePreview)) { hid_handler.command_rgb_led_color((unsigned char)(rs.color[0] * 255.0f), (unsigned char)(rs.color[1] * 255.0f), (unsigned char)(rs.color[2] * 255.0f)); } ImGui::PopStyleColor(pushed_styles); ImGui::PopItemWidth(); ImGui::TableNextColumn(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); ImGui::Text(LSTR(StringID::LABEL_USAGE_TIME).c_str()); ImGui::Text(format_hours_minutes(hid_handler.displays_on).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::Text(LSTR(StringID::LABEL_LONGEST_SESSION).c_str()); ImGui::Text(format_hours_minutes(hid_handler.longest_displays_on).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::Text("%s %s", LSTR(StringID::LABEL_BEYOND_FIRMWARE).c_str(), !hid_handler.firmware_version.empty() ? ("v" + hid_handler.firmware_version).c_str() : LSTR(StringID::STATUS_LOADING).c_str()); if (!hid_handler.firmware_version.empty() && rs.fw.mcu_update_available(hid_handler.firmware_version)) { ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - g_Dpi.F(3)); std::string btn_id = LSTR(StringID::BUTTON_UPDATE_AVAILABLE); pushed_styles = PushButtonStyle(); if (ImGui::Button((btn_id + "##1").c_str(), g_Dpi.Vec2(0, 20))) { rs.pending_popup = "Beyond Firmware"; } ImGui::PopStyleColor(pushed_styles); } // Display utility option for eyetracking if (rs.has_eyetracking) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); if (!rs.dfu_update_requested && hid_handler.et_cam_version.empty() && now - rs.last_et_firmware_check >= 5) { hid_handler.et_cam_version = hid_handler.get_device_rev("VID_35BD", "PID_0202"); rs.last_et_firmware_check = now; } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::Text(LSTR(StringID::LABEL_EYETRACKING).c_str()); ImGui::SameLine(); if (rs.installed_et_driver) { auto et_s = get_et_status_string(); ImGui::PushStyleColor(ImGuiCol_Text, et_s.second); ImGui::Text(et_s.first.c_str()); ImGui::PopStyleColor(); } else { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0, 0, 1)); ImGui::Text(LSTR(StringID::STATUS_NOT_INSTALLED).c_str()); ImGui::PopStyleColor(); } ImGui::Spacing(); ImGui::Text("%s %s", LSTR(StringID::LABEL_CAMERA_FIRMWARE).c_str(), !hid_handler.et_cam_version.empty() ? ("v" + hid_handler.et_cam_version).c_str() : LSTR(StringID::STATUS_LOADING).c_str()); if (!hid_handler.et_cam_version.empty() && rs.fw.dfu_update_available(hid_handler.et_cam_version) && rs.installed_et_driver) { ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - g_Dpi.F(3)); std::string btn_id = LSTR(StringID::BUTTON_UPDATE_AVAILABLE); pushed_styles = PushButtonStyle(); if (ImGui::Button((btn_id + "##2").c_str(), g_Dpi.Vec2(0, 20))) { rs.pending_popup = "Eyetracking Firmware"; } ImGui::PopStyleColor(pushed_styles); } ImGui::Spacing(); if (!rs.installed_et_driver) { pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_INSTALL_EYETRACKING_ADDON).c_str(), g_Dpi.Vec2(-1, 20))) { if (utils.install_et_driver(rs)) { set_steamvr_settings.store(true); } } ImGui::PopStyleColor(pushed_styles); } else { pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_SETTINGS).c_str(), g_Dpi.Vec2(-1, 20))) { rs.current_page = Page_Type::ETSettings; } ImGui::PopStyleColor(pushed_styles); } ImGui::Spacing(); pushed_styles = PushCheckboxStyle(); bool et_enabled = rs.et_enabled; ImGui::Checkbox("##et_runtime_enabled", &et_enabled); ImGui::PopStyleColor(pushed_styles); ImGui::SameLine(); ImGui::Text(LSTR(StringID::EYETRACKING_ENABLE_EYETRACKING).c_str()); if (et_enabled != rs.et_enabled) { rs.et_enabled = et_enabled; utils.write_settings_file(rs); } } ImGui::Spacing(); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Dummy(ImVec2(0.0f, ImGui::GetContentRegionAvail().y - g_Dpi.F(260.0))); // Save button and description are meant to be bottom-aligned if (saving.load()) { ImGui::Text(LSTR(StringID::STATUS_SAVING).c_str()); pushed_styles = PushProgressBarStyle(); ImGui::ProgressBar((float)saving_progress.load() / 100.0f); ImGui::PopStyleColor(pushed_styles); saving_progress += 2; if (saving_progress >= 100) { // Restart device if (reboot_after_save) { reboot_after_save.store(false); Sleep(1000); hid_handler.cdcommand_restart(); } saving.store(false); saving_progress.store(0); } } else { ImGui::TextWrapped(LSTR(StringID::DESC_SAVE_SETTINGS).c_str()); ImGui::Spacing(); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_SAVE_SETTINGS).c_str(), g_Dpi.Vec2(-1, 20))) { saving.store(true); // Write current config out with current GUI options. if (!utils.update_config(hid_handler, rs)) { MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_FAILED_WRITE_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); }; // Save GUI settings. utils.write_settings_file(rs); } ImGui::PopStyleColor(pushed_styles); } ImGui::PopItemWidth(); ImGui::EndTable(); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_CHANGE_STEAMVR_FOLDER).c_str(), g_Dpi.Vec2(200, 20)) && !rs.opened_folder_select) { rs.opened_folder_select = true; rs.steam_config_path.clear(); std::thread([](HWND hwndOwner, UI_Run_State& rs) { TCHAR szDir[MAX_PATH]; BROWSEINFO bInfo = {}; bInfo.hwndOwner = hwndOwner; bInfo.pidlRoot = NULL; bInfo.pszDisplayName = szDir; bInfo.lpszTitle = LWSTR(StringID::FILE_DIALOG_SELECT_STEAMVR_FOLDER).c_str(); bInfo.ulFlags = BIF_NEWDIALOGSTYLE; bInfo.lpfn = NULL; bInfo.lParam = 0; bInfo.iImage = -1; LPITEMIDLIST lpItem = SHBrowseForFolder(&bInfo); if (lpItem != NULL) { wchar_t selectedPath[MAX_PATH]; if (SHGetPathFromIDList(lpItem, selectedPath)) { rs.steamvr_path = selectedPath; std::wstring selectedFolder = selectedPath; std::wstring folderName = PathFindFileName(selectedFolder.c_str()); std::wstring steamvrPath = selectedFolder; if (folderName != L"SteamVR") { size_t pos = selectedFolder.find_last_of(L"\\/"); if (pos != std::wstring::npos) { steamvrPath = selectedFolder.substr(0, pos); // Get parent directory } } SHGetPathFromIDList(lpItem, szDir); rs.exe_path = steamvrPath + L"\\tools\\lighthouse\\bin\\win64\\lighthouse_console.exe"; rs.valid_path = std::filesystem::exists(rs.exe_path.c_str()) && std::filesystem::is_regular_file(rs.exe_path.c_str()); if (rs.valid_path) { WCHAR fixedpath[MAX_PATH]; if (SUCCEEDED(PathCchCanonicalize(fixedpath, MAX_PATH, steamvrPath.c_str()))) { rs.steamvr_path = fixedpath; utils.write_settings_file(rs); } } } } rs.opened_folder_select = false; rs.checked_steamvr_settings = false; }, glfwGetWin32Window(window), std::ref(rs)).detach(); } ImGui::PopStyleColor(pushed_styles); if (!rs.valid_path) { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); ImGui::Text(LSTR(StringID::ERROR_UTILITY_INVALID_STEAMVR).c_str()); ImGui::PopStyleColor(); } else { ImGui::Text("%s", (std::string(rs.steamvr_path.begin(), rs.steamvr_path.end()).c_str())); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); pushed_styles = PushButtonStyleGrey(); // Utility version as advanced settings button std::string version = (LSTR(StringID::UTILITY_VERSION) + " " + ui.app_version); auto windowWidth = ImGui::GetContentRegionAvail().x; auto textWidth = ImGui::CalcTextSize(version.c_str()).x; float cur_y = ImGui::GetCursorPosY(); ImGui::SetCursorPosX((windowWidth - textWidth) * 0.5f); ImGui::SetCursorPosY(cur_y - g_Dpi.F(3)); if (ImGui::Button(version.c_str())) { rs.current_page = Page_Type::Advanced; } ImGui::SetCursorPos(ImVec2(windowWidth - g_Dpi.F(30), cur_y - g_Dpi.F(5))); // Language button Lang_Setting active_lang_setting = utils.get_locale_settings(rs); auto [flag, w, h] = tex_loader.GetPng(active_lang_setting.flag_path); if (ImGui::ImageButton("##localebtn", (void*)(intptr_t)flag, g_Dpi.Vec2(30, 20))) { int nextIndex = ((int)rs.active_locale % ((int)Locales::Max - 1)) + 1; rs.active_locale = (Locales)nextIndex; // Apply change Localization::SetLanguage(rs.active_locale); utils.write_settings_file(rs); } ImGui::PopStyleColor(pushed_styles); if (loading_something) { ImGui::EndDisabled(); } } void UI::show_steamvr_flagged_settings(UI_Run_State& rs) { for (const SteamVR_Warned_Settings setting_Type : rs.flagged_settings) { SteamVR_Flagged_Setting setting_details = warned_settings.at(setting_Type); ImGui::PushID(static_cast(setting_Type)); // Create a horizontal layout for the title and description ImGui::Columns(3, nullptr, false); // Title ImGui::SetColumnWidth(0, g_Dpi.F(170)); ImGui::TextWrapped(setting_details.title.c_str()); // Description ImGui::NextColumn(); ImGui::SetColumnWidth(1, g_Dpi.F(320)); pushed_styles = PushSubTextStyle(); ImGui::TextWrapped(setting_details.description.c_str()); ImGui::PopStyleColor(pushed_styles); // Resolve ImGui::NextColumn(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() + g_Dpi.F(30)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_RESOLVE).c_str(), g_Dpi.Vec2(65, 0))) { if (utils.write_steamvr_settings(rs, setting_details)) { set_steamvr_settings.store(true); rs.checked_steamvr_settings = false; } else { MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_FAILED_STEAMVR_SETTINGS).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } } ImGui::SetCursorPosX(ImGui::GetCursorPosX() + g_Dpi.F(30)); if (ImGui::Button(LSTR(StringID::BUTTON_IGNORE).c_str(), g_Dpi.Vec2(65, 0))) { rs.staged_ignore_setting = setting_Type; rs.pending_popup = "Ignore Setting"; } ImGui::PopStyleColor(pushed_styles); ImGui::PopID(); ImGui::Columns(1); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); } centered_textwrapped(LSTR(StringID::DESC_STEAMVR_SETTINGS_WARNING)); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); pushed_styles = PushButtonStyle(); if (centered_button(LSTR(StringID::BUTTON_BACK).c_str(), g_Dpi.Vec2(120, 0))) { rs.current_page = Page_Type::Home; } ImGui::PopStyleColor(pushed_styles); } void UI::show_advanced_settings(UI_Run_State& rs) { int lower_control_spacing_x = g_Dpi.F(188); // How far from the setting labels that the input controls are offset by auto fw_info = utils.split_headset_firmware(); bool prox_not_supported = std::get<0>(fw_info) == 0 && std::get<1>(fw_info) <= 3 && std::get<2>(fw_info) <= 14; bool sleep_not_supported = std::get<0>(fw_info) == 0 && std::get<1>(fw_info) <= 3 && std::get<2>(fw_info) <= 17; if (font_header) { ImGui::PushFont(font_header); ImGui::Text(LSTR(StringID::LABEL_ADVANCED_SETTINGS).c_str()); ImGui::PopFont(); } ImGui::Spacing(); ImGui::TextWrapped("%s %s | %s %s | %s v%s | %s v%s", LSTR(StringID::ADV_HEADSET_SERIAL).c_str(), hid_handler.hmd_serial.c_str(), LSTR(StringID::ADV_LIGHTHOUSE_SERIAL).c_str(), hid_handler.tundra_serial.c_str(), LSTR(StringID::ADV_BEYOND_FIRMWARE_VERSION).c_str(), hid_handler.firmware_version.c_str(), LSTR(StringID::ADV_ET_FIRMWARE_VERSION).c_str(), (hid_handler.et_cam_version.empty() ? "00" : hid_handler.et_cam_version.c_str())); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); centered_text(LSTR(StringID::ADV_FLASH_BEYOND_FIRMWARE)); pushed_styles = PushButtonStyle(); ImGui::PushID("device_select"); if (centered_button(LSTR(StringID::BUTTON_SELECT_FILE).c_str(), g_Dpi.Vec2(120, 0))) { std::wstring selected_file = L""; std::wstring filter = (LWSTR(StringID::FILE_DIALOG_BEYOND_FIRMWARE) + L" (*.beyondfw)"); filter.push_back(L'\0'); filter += L"*.beyondfw"; filter.push_back(L'\0'); filter.push_back(L'\0'); selected_file = utils.open_file_select(filter.c_str()); if (!selected_file.empty() && rs.fw.load_firmware_file(selected_file)) { rs.current_page = Page_Type::Home; rs.pending_popup = "Beyond Firmware"; } } ImGui::PopID(); ImGui::PopStyleColor(pushed_styles); ImGui::Separator(); centered_text(LSTR(StringID::ADV_FLASH_ET_FIRMWARE)); pushed_styles = PushButtonStyle(); ImGui::PushID("eyetracking_select"); if (centered_button(LSTR(StringID::BUTTON_SELECT_FILE).c_str(), g_Dpi.Vec2(120, 0))) { std::wstring selected_file = L""; std::wstring filter = (LWSTR(StringID::FILE_DIALOG_ET_FIRMWARE) + L" (*.dfu)"); filter.push_back(L'\0'); filter += L"*.dfu"; filter.push_back(L'\0'); filter.push_back(L'\0'); selected_file = utils.open_file_select(filter.c_str()); if (!selected_file.empty() && rs.fw.get_dfu_version(selected_file)) { rs.current_page = Page_Type::Home; rs.dfu_firmware_path = selected_file; rs.pending_popup = "Eyetracking Firmware"; } } ImGui::PopID(); ImGui::PopStyleColor(pushed_styles); ImGui::Separator(); centered_text(LSTR(StringID::ADV_FLASH_LIGHTHOUSE_CONFIG)); pushed_styles = PushButtonStyle(); ImGui::PushID("lighthouse_select"); if (centered_button(LSTR(StringID::BUTTON_SELECT_FILE).c_str(), g_Dpi.Vec2(120, 0))) { if (!hid_handler.tundra_serial.empty()) { std::wstring selected_file = L""; std::wstring filter = (LWSTR(StringID::FILE_DIALOG_LIGHTHOUSE_CONFIG) + L" (*.json)"); filter.push_back(L'\0'); filter += L"*LHR*.json"; filter.push_back(L'\0'); filter.push_back(L'\0'); selected_file = utils.open_file_select(filter.c_str()); if (!selected_file.empty()) { rs.lighthouse_restore_path = selected_file; rs.current_page = Page_Type::Home; rs.pending_popup = "Lighthouse Update"; } } else { MessageBox(glfwGetWin32Window(ui.window), LWSTR(StringID::ERROR_NO_SERIAL).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } } ImGui::PopID(); ImGui::PopStyleColor(pushed_styles); ImGui::Separator(); centered_text(LSTR(StringID::ADV_FLASH_MEMORY_CONFIG)); pushed_styles = PushButtonStyle(); ImGui::PushID("memory_select"); if (centered_button(LSTR(StringID::BUTTON_SELECT_FILE).c_str(), g_Dpi.Vec2(120, 0))) { std::wstring selected_file = L""; std::wstring filter = (LWSTR(StringID::FILE_DIALOG_MEMORY_CONFIG) + L" (*.json)"); filter.push_back(L'\0'); filter += L"*memory*.json"; filter.push_back(L'\0'); filter.push_back(L'\0'); selected_file = utils.open_file_select(filter.c_str()); if (!selected_file.empty()) { rs.memory_restore_path = selected_file; rs.current_page = Page_Type::Home; rs.pending_popup = "Memory Update"; } } ImGui::PopID(); ImGui::PopStyleColor(pushed_styles); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); // Proximity sensor setting float window_edge = (float)g_Dpi.F(window_width) - g_Dpi.F(2) * ImGui::GetStyle().FramePadding.x; float prox_spacing = std::fmax(lower_control_spacing_x, ImGui::CalcTextSize(LSTR(StringID::LABEL_PROXIMITY_SENSOR_OFFSET).c_str()).x + g_Dpi.F(15)); ImGui::SetCursorPosX(prox_spacing); if (ImGui::BeginTable("##proxdirlabel", 2, ImGuiTableFlags_BordersInnerV)) { ImGui::TableSetupColumn("##first", ImGuiTableColumnFlags_WidthFixed, 196); ImGui::TableSetupColumn("##second", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text(LSTR(StringID::LABEL_OUTWARD).c_str()); ImGui::TableNextColumn(); auto textWidth = ImGui::CalcTextSize("Inward").x; ImGui::SetCursorPosX(window_edge - textWidth); ImGui::Text(LSTR(StringID::LABEL_INWARD).c_str()); ImGui::EndTable(); } ImVec2 control_vect = ImGui::GetCursorPos(); control_vect.x = prox_spacing; if (prox_not_supported) { ImGui::BeginDisabled(); } ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(5)); ImGui::Text(LSTR(StringID::LABEL_PROXIMITY_SENSOR_OFFSET).c_str()); ImGui::SameLine(); pushed_styles = PushSliderStyle(); ImGui::SetCursorPos(control_vect); ImGui::PushItemWidth(window_edge - control_vect.x); if (ImGui::SliderInt("##proxoff", &rs.prox_offset, UIConstants.prox_min, UIConstants.prox_max, "%d", ImGuiSliderFlags_NoInput)) { uint16_t converted = (uint16_t)(rs.prox_offset); hid_handler.command_proximity_offset(converted); } if (ImGui::IsItemDeactivatedAfterEdit()) { rs.pending_popup = "Proximity Change"; } if (prox_not_supported) { ImGui::EndDisabled(); } if (prox_not_supported && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(LSTR(StringID::TOOLTIP_PROXIMITY_NOT_SUPPORTED).c_str()); } ImGui::PopItemWidth(); ImGui::PopStyleColor(pushed_styles); ImGui::PopStyleVar(); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); // SteamVR color tint fields Json steamvr_json = utils.get_steamvr_settings(rs); bool invalid = steamvr_json.is_null() || (!steamvr_json.is_null() && steamvr_json["steamvr"].is_null()); std::string r_current = !invalid && !steamvr_json["steamvr"]["hmdDisplayColorGainR"].is_null() ? std::format("{:.1f}", steamvr_json["steamvr"]["hmdDisplayColorGainR"].number_value()) : "1.0"; std::string g_current = !invalid && !steamvr_json["steamvr"]["hmdDisplayColorGainG"].is_null() ? std::format("{:.1f}", steamvr_json["steamvr"]["hmdDisplayColorGainG"].number_value()) : "1.0"; std::string b_current = !invalid && !steamvr_json["steamvr"]["hmdDisplayColorGainB"].is_null() ? std::format("{:.1f}", steamvr_json["steamvr"]["hmdDisplayColorGainB"].number_value()) : "1.0"; bool hovered = false; if (invalid) { ImGui::BeginDisabled(); } ImGui::Spacing(); ImGui::Spacing(); ImGui::Text(LSTR(StringID::LABEL_DISPLAY_COLOR_TINT).c_str()); ImGui::SameLine(); ImGui::SetCursorPosX(std::fmax(lower_control_spacing_x, ImGui::CalcTextSize(LSTR(StringID::LABEL_DISPLAY_COLOR_TINT).c_str()).x + g_Dpi.F(15))); pushed_styles = PushTextInputHintStyle(); ImGui::SetNextItemWidth(56); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(2)); if (ImGui::InputTextWithHint("R", r_current.c_str(), rs.tint_r, sizeof(rs.tint_r))) { utils.set_steamvr_color_gain(rs, L"steamvr.hmdDisplayColorGainR", rs.tint_r); } hovered = !hovered ? ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) : hovered; ImGui::SameLine(); ImGui::SetNextItemWidth(56); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(2)); if (ImGui::InputTextWithHint("G", g_current.c_str(), rs.tint_g, sizeof(rs.tint_g))) { utils.set_steamvr_color_gain(rs, L"steamvr.hmdDisplayColorGainG", rs.tint_g); } hovered = !hovered ? ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) : hovered; ImGui::SameLine(); ImGui::SetNextItemWidth(56); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(2)); if (ImGui::InputTextWithHint("B", b_current.c_str(), rs.tint_b, sizeof(rs.tint_b))) { utils.set_steamvr_color_gain(rs, L"steamvr.hmdDisplayColorGainB", rs.tint_b); } hovered = !hovered ? ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) : hovered; ImGui::SameLine(); ImGui::PopStyleColor(pushed_styles); pushed_styles = PushButtonStyle(); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(2)); if (ImGui::Button(LSTR(StringID::BUTTON_RESET).c_str())) { snprintf(rs.tint_r, sizeof(rs.tint_r), "%.1f", 1.0); snprintf(rs.tint_g, sizeof(rs.tint_g), "%.1f", 1.0); snprintf(rs.tint_b, sizeof(rs.tint_b), "%.1f", 1.0); utils.set_steamvr_color_gain(rs, L"steamvr.hmdDisplayColorGainR", rs.tint_r); utils.set_steamvr_color_gain(rs, L"steamvr.hmdDisplayColorGainG", rs.tint_g); utils.set_steamvr_color_gain(rs, L"steamvr.hmdDisplayColorGainB", rs.tint_b); } hovered = !hovered ? ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) : hovered; ImGui::PopStyleColor(pushed_styles); ImGui::Spacing(); if (invalid) { ImGui::EndDisabled(); } if (hovered && invalid) { show_tooltip(LSTR(StringID::TOOLTIP_STEAMVR_FOLDER_REQUIRED).c_str()); } pushed_styles = PushSubTextStyle(); ImGui::TextWrapped(LSTR(StringID::DESC_COLOR_TINT).c_str()); ImGui::PopStyleColor(pushed_styles); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); // VXR sleep feature if (sleep_not_supported) { ImGui::BeginDisabled(); } ImGui::Text(LSTR(StringID::LABEL_DEVICE_IDLE_SLEEP).c_str()); ImGui::SameLine(); ImGui::SetCursorPosX(std::fmax(lower_control_spacing_x, ImGui::CalcTextSize(LSTR(StringID::LABEL_DEVICE_IDLE_SLEEP).c_str()).x + g_Dpi.F(15))); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(3)); pushed_styles = PushCheckboxStyle(); if (ImGui::Checkbox("##VXRToggle", &rs.vxr_auto_off)) { rs.pending_popup = "Idle Sleep Confirm"; } ImGui::PopStyleColor(pushed_styles); if (sleep_not_supported) { ImGui::EndDisabled(); } if (sleep_not_supported && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(LSTR(StringID::TOOLTIP_SLEEP_NOT_SUPPORTED).c_str()); } ImGui::Spacing(); pushed_styles = PushSubTextStyle(); ImGui::Text(LSTR(StringID::DESC_IDLE_SLEEP).c_str()); ImGui::TextWrapped(LSTR(StringID::DESC_IDLE_SLEEP_NOTE).c_str()); ImGui::PopStyleColor(pushed_styles); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); centered_textwrapped(LSTR(StringID::ADV_ONLY_IF_INSTRUCTED)); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); pushed_styles = PushButtonStyle(); if (centered_button(LSTR(StringID::BUTTON_BACK).c_str(), g_Dpi.Vec2(120, 0))) { rs.current_page = Page_Type::Home; } ImGui::PopStyleColor(pushed_styles); } void UI::show_eyetracking_settings(UI_Run_State& rs) { if (font_header) { ImGui::PushFont(font_header); centered_text(LSTR(StringID::EYETRACKING_HEADER).c_str()); ImGui::PopFont(); } int toggle_columns = 2; ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); if (font_header) { ImGui::PushFont(font_header); ImGui::Text(LSTR(StringID::EYETRACKING_RECIPIENTS_HEADER).c_str()); ImGui::PopFont(); } ImGui::Spacing(); ImGui::Spacing(); ImGui::BeginTable("TogglesTable", toggle_columns); for (int i = 0; i < subscribers.size() / toggle_columns; i++) { ImGui::TableSetupColumn("Col1", ImGuiTableColumnFlags_WidthFixed, g_Dpi.F(165)); } for (int i = 0; i < subscribers.size(); i++) { if (i % toggle_columns == 0) { ImGui::TableNextRow(); } ImGui::TableNextColumn(); ET_Subscriber s_info = subscribers[i]; if (s_info.IsButton) { ImGui::PushID(s_info.ID.c_str()); ImGui::PushFont(font_header); pushed_styles = PushButtonBoxStyle(); if (ImGui::Button(s_info.ID.c_str(), g_Dpi.Vec2(27, 27))) { s_info.Set(false); }; ImVec2 min = ImGui::GetItemRectMin(); ImVec2 max = ImGui::GetItemRectMax(); // Shift up text by a few pixels std::string text = ">"; auto size = ImGui::CalcTextSize(text.c_str()); float x = min.x + (max.x - min.x - size.x) * 0.5f; float y = min.y + (max.y - min.y - size.y) * 0.5f; ImGui::SetCursorPos(ImVec2(x,y)); ImGui::Text(text.c_str()); ImGui::PopStyleColor(pushed_styles); ImGui::PopFont(); ImGui::SameLine(); ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPosX() + g_Dpi.F(8), ImGui::GetCursorPosY() + g_Dpi.F(6))); ImGui::Text(s_info.Label.c_str()); ImGui::PopID(); } else { ImGui::PushID(s_info.ID.c_str()); ImGui::SetWindowFontScale(1.5); bool val = s_info.Get(); pushed_styles = PushCheckboxStyle(); ImGui::Checkbox(s_info.ID.c_str(), &val); ImGui::PopStyleColor(pushed_styles); ImGui::SetWindowFontScale(1); ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y + g_Dpi.F(3)); ImGui::Text(s_info.Label.c_str()); ImGui::PopID(); if (s_info.Get() != val) { s_info.Set(val); } } // Tooltip label ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(2)); ImGui::SetCursorPosX(ImGui::GetCursorScreenPos().x - g_Dpi.F(4)); ImGui::Text("(?)"); ImGui::PopStyleColor(); if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(s_info.Tooltip.c_str()); } } ImGui::EndTable(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); if (font_header) { ImGui::PushFont(font_header); ImGui::Text(LSTR(StringID::EYETRACKING_ENROLLMENT_HEADER).c_str()); ImGui::PopFont(); } ImGui::Spacing(); ImGui::Spacing(); // Disable the enroll button if no valid user ID exists (which requires a valid token) if (rs.auth_status != Auth_Code::Success) { ImGui::BeginDisabled(); } // Add button to change to enroll scene pushed_styles = PushButtonStyle(); if (ImGui::Button(modelManager.MasterModelList().size() > 0 ? LSTR(StringID::EYETRACKING_REDO_ENROLL).c_str() : LSTR(StringID::EYETRACKING_PERFORM_ENROLL).c_str())) { rs.pending_popup = "Enroll Confirm"; } ImGui::PopStyleColor(pushed_styles); // End the disabled state if it was enabled if (rs.auth_status != Auth_Code::Success) { ImGui::EndDisabled(); // Show tooltip explaining why button is disabled if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { show_tooltip(LSTR(StringID::EYETRACKING_ENROLL_REQ).c_str()); } } ImGui::Spacing(); // Create a horizontal layout for the token field and paste button ImGui::BeginGroup(); // Token input field ImGui::Text(LSTR(StringID::EYETRACKING_USER_TOKEN_LABEL).c_str()); ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(3)); pushed_styles = PushTextInputHintStyle(); std::string tempToken = userManager.TestingToken(); tempToken.resize(256); if (ImGui::InputTextWithHint("##TestingToken", LSTR(StringID::EYETRACKING_USER_TOKEN_HINT).c_str(), tempToken.data(), tempToken.size())) { rs.token_input_changed = true; tempToken.resize(strlen(tempToken.data())); } ImGui::PopStyleColor(pushed_styles); // Only make request if the field is no longer in focus if (!ImGui::IsItemActive() && rs.token_input_changed) { rs.token_input_changed = false; userManager.SetTestingToken(tempToken); hid_handler.log << "User token updated!" << std::endl; } // Paste button ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(3)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_PASTE).c_str())) { // Get clipboard content std::string clipboardText = utils.get_clipboard(); if (!clipboardText.empty()) { if (clipboardText.size() > 256) { clipboardText.resize(256); } userManager.SetTestingToken(clipboardText); hid_handler.log << "User token pasted from clipboard." << std::endl; } } ImGui::PopStyleColor(pushed_styles); ImGui::EndGroup(); if (rs.auth_status != Auth_Code::Success) { ImGui::Spacing(); if (rs.auth_status == Auth_Code::InvalidToken) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_ENROLLMENT_INTRO_MESSAGE).c_str()); } if (rs.auth_status == Auth_Code::ExpiredToken) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_TOKEN_EXPIRED_MESSAGE).c_str()); } ImGui::TextWrapped(LSTR(StringID::EYETRACKING_TOKEN_CONTACT_PREFIX).c_str()); if (rs.active_locale != Locales::Jp) { ImGui::SameLine(); } ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.hyperlink_text); ImGui::Text("support@bigscreenvr.com"); ImGui::PopStyleColor(); if (ImGui::IsItemClicked()) { utils.open_link(UIConstants.mail_link); } ImGui::SameLine(); ImGui::Text(LSTR(StringID::EYETRACKING_TOKEN_CONTACT_SUFFIX).c_str()); } else { // Display user ID field ImGui::Text(LSTR(StringID::EYETRACKING_USER_ID_LABEL).c_str()); ImGui::SameLine(); ImGui::Text(!userManager.UserId().empty() ? userManager.UserId().c_str() : "N/A"); } ImGui::Spacing(); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); // Display the dropdown of models show_models(rs); // Display status ImGui::Text(LSTR(StringID::EYETRACKING_STATUS_LABEL).c_str()); ImGui::SameLine(); auto et_s = get_et_status_string(); ImGui::PushStyleColor(ImGuiCol_Text, et_s.second); ImGui::Text(et_s.first.c_str()); ImGui::PopStyleColor(); ImGui::SameLine(); std::string adv_btn_text = LSTR(StringID::LABEL_ADVANCED_SETTINGS); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(3)); ImGui::SetCursorPosX(ImGui::GetWindowSize().x - (ImGui::CalcTextSize(adv_btn_text.c_str()).x + g_Dpi.F(15))); pushed_styles = PushButtonStyle(); if (ImGui::Button(adv_btn_text.c_str())) { rs.pending_popup = "Eyetracking Advanced Settings"; } ImGui::PopStyleColor(pushed_styles); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); pushed_styles = PushButtonStyle(); if (centered_button(LSTR(StringID::BUTTON_BACK).c_str(), g_Dpi.Vec2(120, 0))) { rs.current_page = Page_Type::Home; } ImGui::PopStyleColor(pushed_styles); } void UI::show_dfu_menu(UI_Run_State& rs) { if (!rs.dfu_update_requested && rs.fw.get_dfu_status() == DFU_Load_Status::Idle) { /* * This section appears if DFU mode is detected * without the user requesting a firmware update. * Otherwise, the firmware update begins automatically when DFU is detected */ ImGui::Text(LSTR(StringID::DFU_DETECTED_MESSAGE).c_str()); ImGui::Text(LSTR(StringID::DFU_EXIT_MESSAGE).c_str()); ImGui::Text(LSTR(StringID::DFU_STUCK_MESSAGE).c_str()); centered(g_Dpi.F(120)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_EXIT_DFU_MODE).c_str(), g_Dpi.Vec2(120, 0))) { rs.fw.clear_dfu_status(); hid_handler.awaiting_device.store(true); hid_handler.leaving_dfu.store(true); hid_handler.device = NULL; hid_handler.device_state.store(Beyond_Device_State::Disconnected); hid_handler.command_fpga_switch(); } ImGui::PopStyleColor(pushed_styles); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); } if (rs.fw.get_dfu_status() == DFU_Load_Status::In_Progress) { float load_progress = rs.fw.dfu_progress.load(); centered_text(LSTR(StringID::STATUS_UPDATING_ET_FIRMWARE)); pushed_styles = PushProgressBarStyle(); ImGui::ProgressBar(load_progress, g_Dpi.Vec2((float)window_width - 15.0f, 20.0f)); ImGui::PopStyleColor(pushed_styles); if (rs.fw.previous_dfu_progress.load() != load_progress) { rs.fw.last_dfu_change = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); rs.fw.previous_dfu_progress.store(load_progress); } } else if (rs.fw.get_dfu_status() == DFU_Load_Status::Success) { centered_text(LSTR(StringID::DFU_UPDATE_COMPLETE)); ImGui::Spacing(); centered(g_Dpi.F(220)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_RESTART_EYETRACKING_CAMERAS).c_str(), g_Dpi.Vec2(220, 0))) { hid_handler.command_fpga_switch(); // Start looking for the eyetracking firmware version hid_handler.et_cam_version = ""; rs.dfu_check_count = 0; rs.fw.dfu_load_status.store(DFU_Load_Status::Verification); } ImGui::PopStyleColor(pushed_styles); } else if (rs.fw.get_dfu_status() == DFU_Load_Status::Verification) { uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); centered_text(LSTR(StringID::STATUS_VERIFYING_UPDATE)); ImGui::Spacing(); centered(g_Dpi.F(220)); if (hid_handler.et_cam_version.empty() && now - rs.last_et_firmware_check >= 5) { if (rs.dfu_check_count < UIConstants.max_dfu_verification_checks) { rs.dfu_check_count++; hid_handler.et_cam_version = hid_handler.get_device_rev("VID_35BD", "PID_0202"); rs.last_et_firmware_check = now; } // Unable to find the firmware version, update load likely failed else { rs.fw.dfu_load_status.store(DFU_Load_Status::Failure); rs.pending_popup = "Eyetracking Update Failed"; } } // Firmware was found, reset to application state if (!hid_handler.et_cam_version.empty()) { rs.dfu_update_requested = false; rs.fw.clear_dfu_status(); hid_handler.awaiting_device.store(true); hid_handler.leaving_dfu.store(true); hid_handler.device = NULL; hid_handler.device_state.store(Beyond_Device_State::Disconnected); } } else if (rs.fw.get_dfu_status() == DFU_Load_Status::Idle) { auto [exists, driverKey] = hid_handler.in_dfu(); // Allow for an update only if the device is in DFU if (exists) { if (rs.dfu_update_requested) { rs.fw.load_dfu_firmware(hid_handler, rs.dfu_firmware_path); rs.dfu_update_requested = false; } else { centered(g_Dpi.F(120)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_UPDATE_FIRMWARE).c_str(), g_Dpi.Vec2(120, 0))) { utils.start_dfu_update(rs); } ImGui::PopStyleColor(pushed_styles); } } else { centered_text(LSTR(StringID::DFU_WAITING_FOR_DFU)); ImGui::Spacing(); centered(g_Dpi.F(100)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(100, 0))) { rs.dfu_update_requested = false; rs.fw.clear_dfu_status(); hid_handler.entering_dfu.store(false); hid_handler.awaiting_device.store(true); hid_handler.leaving_dfu.store(true); hid_handler.device = NULL; hid_handler.device_state.store(Beyond_Device_State::Disconnected); } ImGui::PopStyleColor(pushed_styles); } } // No other status is handled, assume a failure else { rs.dfu_update_requested = false; ImGui::Text(LSTR(StringID::DFU_UPDATE_FAILED).c_str()); ImGui::Text("Status code: %d", rs.fw.get_dfu_status()); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); if (rs.fw.get_dfu_status() == DFU_Load_Status::DFU_File_Invalid) { ImGui::Text(LSTR(StringID::DFU_INTEGRITY).c_str()); } else if (rs.fw.get_dfu_status() == DFU_Load_Status::Firmware_Not_Found) { ImGui::Text(LSTR(StringID::DFU_FW_NOT_FOUND).c_str()); } else { ImGui::Text(LSTR(StringID::DFU_RETRY).c_str()); } ImGui::PopStyleColor(); ImGui::Spacing(); pushed_styles = PushButtonStyle(); if (rs.fw.get_dfu_status() == DFU_Load_Status::DFU_File_Invalid || rs.fw.get_dfu_status() == DFU_Load_Status::Firmware_Not_Found) { if (ImGui::Button(LSTR(StringID::BUTTON_EXIT_DFU_MODE).c_str(), g_Dpi.Vec2(120, 0))) { rs.fw.clear_dfu_status(); hid_handler.awaiting_device.store(true); hid_handler.leaving_dfu.store(true); hid_handler.device = NULL; hid_handler.device_state.store(Beyond_Device_State::Disconnected); hid_handler.command_fpga_switch(); } } else { if (ImGui::Button(LSTR(StringID::BUTTON_UPDATE_FIRMWARE).c_str(), g_Dpi.Vec2(120, 0))) { utils.start_dfu_update(rs); } } ImGui::PopStyleColor(pushed_styles); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); centered_text(std::format("{} {}", LSTR(StringID::UTILITY_VERSION), ui.app_version)); } void UI::show_bootloader_menu(UI_Run_State& rs) { if (rs.first_time_in_bootloader) { rs.first_time_in_bootloader = false; rs.fw.clear_status(); hid_handler.bl_request_software_version.store(false); hid_handler.bl_got_status.store(false); } if (rs.launching_bootloader) { rs.launching_bootloader = false; } if (!rs.firmware_update_requested) { /* * This section appears if bootloader mode is detected * without the user requesting a firmware update. * Otherwise, the firmware update begins automatically when * the bootloader is detected. */ ImGui::Text(LSTR(StringID::BOOTLOADER_DETECTED).c_str()); if (hid_handler.blfirmware_version != "") { ImGui::Text("%s %s", LSTR(StringID::BOOTLOADER_FIRMWARE_VERSION), hid_handler.blfirmware_version.c_str()); } else { hid_handler.blcommand_software_version(); } ImGui::Text(LSTR(StringID::BOOTLOADER_RESTART_MESSAGE).c_str()); ImGui::Text(LSTR(StringID::BOOTLOADER_STUCK_MESSAGE).c_str()); centered(g_Dpi.F(120)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_RESTART_BEYOND).c_str(), g_Dpi.Vec2(120, 0))) { hid_handler.blcommand_restart(); } ImGui::PopStyleColor(pushed_styles); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); } if (rs.fw.get_update_status() == Firmware_Status::In_Progress) { centered_text(LSTR(StringID::STATUS_UPDATING_FIRMWARE)); pushed_styles = PushProgressBarStyle(); ImGui::ProgressBar((float)rs.fw.get_update_progress(), g_Dpi.Vec2((float)window_width - 15.0f, 20.0f)); ImGui::PopStyleColor(pushed_styles); } else if (rs.fw.get_update_status() == Firmware_Status::Success) { centered_text(LSTR(StringID::BOOTLOADER_UPDATE_COMPLETE)); centered(g_Dpi.F(120)); if (rs.firmware_update_requested) { pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_RESTART_BEYOND).c_str(), g_Dpi.Vec2(120, 0))) { rs.firmware_update_requested = false; hid_handler.blcommand_restart(); } ImGui::PopStyleColor(pushed_styles); } } else if (rs.fw.get_update_status() == Firmware_Status::Idle) { if (rs.firmware_update_requested) { rs.fw.update_firmware(hid_handler); } else { centered(g_Dpi.F(120)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_UPDATE_FIRMWARE).c_str(), g_Dpi.Vec2(120, 0))) { rs.fw.clear_status(); rs.fw.update_firmware(hid_handler); } ImGui::PopStyleColor(pushed_styles); } } else { rs.firmware_update_requested = false; ImGui::Text(LSTR(StringID::BOOTLOADER_UPDATE_FAIL).c_str()); ImGui::Text("Status code: %d", rs.fw.get_update_status()); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); centered_text(std::format("{} {}", LSTR(StringID::UTILITY_VERSION), ui.app_version)); } void UI::show_crashhandler_menu(UI_Run_State& rs) { if (rs.first_time_in_crashhandler) { rs.first_time_in_crashhandler = false; // Launch the crash dump logger hid_handler.crashdump_status.store(Crash_Dump_State::Begin); rs.cl_state = Crash_Log_State::Begin; hid_handler.cdcommand_get_crash_dump(); } ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); // Error in red text ImGui::Text(LSTR(StringID::CRASH_COMMUNICATION_ERROR).c_str()); ImGui::PopStyleColor(); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); if (hid_handler.crashdump_status.load() >= Crash_Dump_State::Got_Name) { if (rs.cl_state == Crash_Log_State::Begin) { rs.cl_state = Crash_Log_State::WaitingMemoryDump; rs.cl_mem_num = 0; rs.error_log_file_name = save_error_log(hid_handler); } ImGui::Text("%s %s", LSTR(StringID::CRASH_SOFTWARE_VERSION), hid_handler.cdinfo.swver.c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::Text(LSTR(StringID::CRASH_ERROR_REASON).c_str()); ImGui::SameLine(); std::string crashrsn; switch (hid_handler.cdinfo.crash_reason) { case Beyond_Crash_Reason::Hard_Fault: ImGui::Text(LSTR(StringID::CRASH_HARD_FAULT).c_str()); break; case Beyond_Crash_Reason::Unknown_ISR: ImGui::Text(LSTR(StringID::CRASH_UNKNOWN_ISR).c_str()); break; case Beyond_Crash_Reason::Stack_Overflow: ImGui::Text(LSTR(StringID::CRASH_STACK_OVERFLOW).c_str(), hid_handler.cdinfo.crashed_task_name.c_str()); break; default: ImGui::Text(LSTR(StringID::CRASH_UNKNOWN_ERROR).c_str()); break; } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); // Show a progress bar if we have mem_region_info if (hid_handler.crashdump_status.load() >= Crash_Dump_State::Got_Mem_Region_Info) { // But not if we have finished if (hid_handler.crashdump_status.load() != Crash_Dump_State::Done) { uint32_t total_mem = 0; for (const auto& minfo : hid_handler.cdinfo.mem_info) { total_mem += std::get<2>(minfo) - std::get<1>(minfo); } ImGui::Text(LSTR(StringID::STATUS_SAVING_ERROR_LOG).c_str()); pushed_styles = PushProgressBarStyle(); ImGui::ProgressBar((float)hid_handler.total_mem_dumped.load() / (float)total_mem); ImGui::PopStyleColor(pushed_styles); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); } } ImGui::Text(LSTR(StringID::CRASH_DISCONNECT_RECONNECT).c_str()); ImGui::Text(LSTR(StringID::CRASH_CONTACT_SUPPORT).c_str()); ImGui::SameLine(); pushed_styles = PushButtonStyle(); if (ImGui::Button("support@bigscreenvr.com")) { ::ShellExecute(NULL, NULL, L"mailto:support@bigscreenvr.com", NULL, NULL, 0); } ImGui::PopStyleColor(pushed_styles); ImGui::Text(LSTR(StringID::CRASH_VISIT_WEBSITE_HEAD).c_str()); ImGui::SameLine(); pushed_styles = PushButtonStyle(); if (ImGui::Button("bigscreenvr.com/mybeyond")) { ::ShellExecute(NULL, NULL, L"https://www.bigscreenvr.com/mybeyond", NULL, NULL, 0); } ImGui::PopStyleColor(pushed_styles); ImGui::SameLine(); ImGui::Text(LSTR(StringID::CRASH_VISIT_WEBSITE_TAIL).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); centered(g_Dpi.F(120)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_RESTART_BEYOND).c_str(), g_Dpi.Vec2(120, 0))) { hid_handler.cdcommand_restart(); } ImGui::PopStyleColor(pushed_styles); } // Save additional memory regions once they have been read from the Beyond if (hid_handler.crashdump_status.load() >= Crash_Dump_State::Got_Mem_Region_Info) { // This "Got_Mem_Region_Info" state is where it is reading memory // If the hid_handler has advanced the region number, then // we can save the one stored in cl_mem_num if (hid_handler.current_mem_region_number.load() > rs.cl_mem_num) { // Save the most recently read memory section to the error log file save_memory_dump(hid_handler, rs.cl_mem_num, rs.error_log_file_name); rs.cl_mem_num++; // After the last memory region is dumped, the "current_mem_region_number" will // remain at +1 past the last region's number. // So we can still check against this value even if the state has switched to // Crash_Dump_State::Done } } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::Spacing(); centered_text(std::format("{} {}", LSTR(StringID::UTILITY_VERSION), ui.app_version)); } void UI::show_DFR_home(UI_Run_State& rs) { auto windowSize = ImGui::GetWindowSize(); if (font_header) { ImGui::PushFont(font_header); ImGui::Text(LSTR(StringID::EYETRACKING_DFR_HEADER).c_str()); ImGui::PopFont(); } ImGui::Spacing(); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_DFR_DESCRIPTION).c_str()); ImGui::Text(LSTR(StringID::EYETRACKING_DFR_GET_STARTED_PREFIX).c_str()); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.6f, 1, 1)); ImGui::Text(LSTR(StringID::EYETRACKING_DFR_TITLES_LINK_TEXT).c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemClicked()) { rs.pending_popup = "DFR Apps"; } if (rs.active_locale != Locales::Jp) { ImGui::SameLine(); } ImGui::Text(LSTR(StringID::EYETRACKING_DFR_GET_STARTED_SUFFIX).c_str()); ImGui::Spacing(); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_DFR_OPENXR_FORWARD_MESSAGE).c_str()); ImGui::Spacing(); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_DFR_QUAD_VIEWS_REQUIRED).c_str()); ImGui::Spacing(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.6f, 1, 1)); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_DFR_DOWNLOAD_INSTALL_LINK).c_str()); ImGui::PopStyleColor(); ImGui::Spacing(); if (ImGui::IsItemClicked()) { utils.open_link(UIConstants.quadviews_link); } ImGui::TextWrapped(LSTR(StringID::EYETRACKING_DFR_RESTART_STEAMVR_MESSAGE).c_str()); ImGui::Spacing(); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_DFR_BETA_NOTE).c_str()); ImGui::Spacing(); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::SetWindowFontScale(UIConstants.et_checkbox_Scale); int pushed_styles = PushCheckboxStyle(); bool next = rs.et_publish_memmap; ImGui::Checkbox("##DFR", &next); ImGui::PopStyleColor(pushed_styles); ImGui::SetWindowFontScale(1); ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y + g_Dpi.F(3)); ImGui::Text(LSTR(StringID::EYETRACKING_ENABLE_DFR).c_str()); if (next != rs.et_publish_memmap) { if (next && !rs.dfr_settings_prompted) { rs.pending_popup = "DFR Confirm"; } else { rs.et_publish_memmap = next; utils.write_settings_file(rs); // Alignment overlay needs DFR, prompt to disable if (!next && rs.enabled_alignment_helper) { rs.pending_popup = "DFR Disable"; rs.et_publish_memmap = true; } } } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_BACK).c_str(), g_Dpi.Vec2(120, 0))) { rs.current_page = Page_Type::ETSettings; } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::EYETRACKING_OPEN_QUAD_VIEW_SETTINGS).c_str())) { rs.pending_popup = "Quad-View Settings"; } ImGui::PopStyleColor(pushed_styles); } #pragma endregion void UI::DFR_quad_settings(UI_Run_State& rs, float popup_totalWidth) { float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; // User settings for (int i = 0; i < dfr_setting_inputs.size(); i++) { auto s_input = dfr_setting_inputs[i]; std::string val = s_input.Get(); ImGui::Separator(); ImGui::Spacing(); ImGui::Text(s_input.Label.c_str()); ImGui::Spacing(); ImGui::PushID(s_input.Label.c_str()); ImGui::SetNextItemWidth(g_Dpi.F(s_input.FieldWidth)); pushed_styles = PushTextInputStyle(); if (ImGui::InputText("", val.data(), 256)) { rs.changed_DFR_setting = true; s_input.Set(val); } ImGui::PopStyleColor(pushed_styles); ImVec2 size = ImGui::CalcTextSize(s_input.Desc.c_str()); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - (size.y / 4)); ImGui::TextWrapped(s_input.Desc.c_str()); ImGui::PopStyleColor(); ImGui::PopID(); } ImGui::Separator(); ImGui::Spacing(); // Gaze cursor checkbox bool usingCursor = rs.quad_debug_gaze > 0 ? true : false; ImGui::SetWindowFontScale(UIConstants.et_checkbox_Scale); pushed_styles = PushCheckboxStyle(); if (ImGui::Checkbox("##cursor", &usingCursor)) { rs.changed_DFR_setting = true; rs.quad_debug_gaze = usingCursor ? 1 : 0; } ImGui::PopStyleColor(pushed_styles); ImGui::SetWindowFontScale(1); ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + g_Dpi.F(3)); ImGui::Text(LSTR(StringID::EYETRACKING_DFR_GAZE_CURSOR).c_str()); std::string cursorDesc = LSTR(StringID::EYETRACKING_VISUAL_CURSOR); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); ImGui::Text(cursorDesc.c_str()); ImGui::PopStyleColor(); if (!ImGui::IsAnyItemActive() && rs.changed_DFR_setting) { ImGui::CloseCurrentPopup(); rs.changed_DFR_setting = false; rs.pending_popup = "DFR Confirm"; } } void UI::show_models(UI_Run_State& rs) { int dotCount = (int)(ImGui::GetTime() * 2) % 4; std::string dots = std::string(dotCount, '.'); if (font_header) { ImGui::PushFont(font_header); const std::string label = std::string(LSTR(StringID::EYETRACKING_AVAILABLE_MODELS).data()) + " ({}) {}"; ImGui::Text(std::vformat(label, std::make_format_args(rs.et_models.size(), rs.expecting_new_model ? " " + dots : "")).c_str()); ImGui::PopFont(); } ImGui::Spacing(); ImGui::Spacing(); float drop_down_width = g_Dpi.F(350); for (int i = 0; i < et_model_filters.size(); i++) { ImGui::SameLine(0, 0); ET_ModelFilterGroup group = et_model_filters[i]; pushed_styles = rs.active_models_filter == group.Filter ? PushButtonCategoryStyleHighlighted() : PushButtonCategoryStyle(); if (ImGui::Button(group.Label.c_str(), ImVec2(drop_down_width / 3, g_Dpi.F(35)))) { rs.active_models_filter = group.Filter; ui.update_models(userManager.UserTrainedModels()); } ImGui::PopStyleColor(pushed_styles); } ImGui::SetWindowFontScale(UIConstants.et_model_ui_Scale); ImGui::SameLine(); // Model edit buttons if (rs.et_selected_model.empty()) { ImGui::BeginDisabled(); } pushed_styles = PushButtonBoxStyle(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() - g_Dpi.F(3)); auto [e_texId, e_w, e_h] = tex_loader.GetPng("images/edit.png"); if (ImGui::ImageButton("##modeledit", (void*)(intptr_t)e_texId, g_Dpi.Vec2(19, 21))) { rs.pending_edit_model = utils.et_format_model_name(rs, rs.et_selected_model, false); rs.pending_popup = "Label Model"; } ImGui::SameLine(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() - g_Dpi.F(3)); auto [d_texId, d_w, d_h] = tex_loader.GetPng("images/delete.png"); if (ImGui::ImageButton("##modelremove", (void*)(intptr_t)d_texId, g_Dpi.Vec2(19, 21))) { rs.pending_edit_model = utils.et_format_model_name(rs, rs.et_selected_model, false); rs.pending_popup = "Remove Model"; } ImGui::PopStyleColor(pushed_styles); if (rs.et_selected_model.empty()) { ImGui::EndDisabled(); } ImGui::SetCursorPosY(ImGui::GetCursorPosY() - g_Dpi.F(8)); // Store the enabled state bool isEnabled = rs.et_models.size() > 0 && !userManager.IsLoadingModel(); std::string comboLabel = isEnabled ? (!rs.et_selected_model.empty() ? utils.et_format_model_name(rs, rs.et_selected_model, false) : LSTR(StringID::EYETRACKING_MODEL_NO_SELECT)) : LSTR(StringID::EYETRACKING_MODEL_NONE_AVAILABLE); if (userManager.IsLoadingModel()) { comboLabel = std::vformat(std::format("{}{{}}", LSTR(StringID::EYETRACKING_STATUS_DOWNLOADING_MODEL)), std::make_format_args(dots)); } pushed_styles = !isEnabled ? PushDropdownStyleInactive() : PushDropdownStyle(); ImGui::PushFont(font_header); ImGui::SetWindowFontScale(UIConstants.et_area_scale); ImGui::SetNextItemWidth(drop_down_width); if (ImGui::BeginCombo("##ModelSelectCombo", comboLabel.c_str())) { if (isEnabled) { for (int i = 0; i < rs.et_models.size(); i++) { if (i >= rs.et_models.size()) { break; } std::string modelPath = utils.string_wide_to_narrow(rs.et_models[i].Name); // Skip if not a .model file if (!std::string(modelPath).ends_with(".model")) { continue; } std::string displayName = utils.et_format_model_name(rs, modelPath, false); // Determine if this model is selected bool isModelSelected = (rs.et_selected_model == modelPath); if (ImGui::Selectable(std::format("{}##model{}", displayName, i).c_str(), isModelSelected)) { // Skip selection if the model path contains a percentage if (!(modelPath.find("(") != std::string::npos && modelPath.find(")") != std::string::npos && modelPath.find("%") != std::string::npos)) { rs.et_selected_model = modelPath; utils.write_settings_file(rs); modelManager.OnModelSelected(rs.et_models[i]); } else { rs.pending_popup = "Model Training"; } } if (isModelSelected) { ImGui::SetItemDefaultFocus(); } } } ImGui::EndCombo(); } ImGui::PopStyleColor(pushed_styles); ImGui::SetWindowFontScale(1); ImGui::PopFont(); if (!isEnabled) { // Show tooltip explaining option if (ImGui::IsItemHovered() && !userManager.IsLoadingModel()) { show_tooltip(!userManager.UserId().empty() ? LSTR(StringID::EYETRACKING_NO_MODELS).c_str() : LSTR(StringID::EYETRACKING_TOKEN_FOR_MODELS).c_str()); } } ImGui::SameLine(); // Add camera toggle checkbox bool current = rs.et_enabled; ImGui::SetWindowFontScale(UIConstants.et_checkbox_Scale); pushed_styles = PushCheckboxStyle(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() - g_Dpi.F(3)); if (ImGui::Checkbox("##0", ¤t)) { rs.et_enabled = current; utils.write_settings_file(rs); } ImGui::PopStyleColor(pushed_styles); ImGui::SetWindowFontScale(1); ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y + g_Dpi.F(3)); ImGui::Text(LSTR(StringID::EYETRACKING_ENABLE_EYETRACKING).c_str()); if (!userManager.ModelLoadingError().empty()) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0, 0, 1)); ImGui::Text(("! " + userManager.ModelLoadingError()).c_str()); ImGui::Text(LSTR(StringID::EYETRACKING_PLEASE_TRY_AGAIN).c_str()); ImGui::PopStyleColor(); } else if (eyetracking_runtime.RuntimeActive()) { ImGui::Spacing(); ImGui::Spacing(); ImGui::Text(LSTR(StringID::EYETRACKING_ALIGNMENT_OFF_QUESTION).c_str()); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.6f, 1, 1)); ImGui::Text(LSTR(StringID::EYETRACKING_SEE_TIPS_LINK).c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemClicked()) { rs.pending_popup = "Accuracy Tips"; } if (rs.et_selected_model.find("v0.1") != std::string::npos || rs.et_selected_model.find("v0.2") != std::string::npos) { ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0, 0, 1)); ImGui::Text(LSTR(StringID::EYETRACKING_MODEL_NO_BLINKING_WARNING).c_str()); ImGui::PopStyleColor(); } else { bool alignmentEnabled = rs.enabled_alignment_helper; ImGui::Spacing(); ImGui::Text(LSTR(StringID::EYETRACKING_SHOW_ALIGNMENT_HELPER).c_str()); ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(3)); pushed_styles = PushCheckboxStyle(); ImGui::SetCursorPosX(ImGui::GetCursorPosX() - g_Dpi.F(3)); if (ImGui::Checkbox("##a", &alignmentEnabled)) { if (alignmentEnabled) { if (rs.et_publish_memmap) { rs.pending_popup = "Enable Alignment Helper"; } else { rs.pending_popup = "Alignment Helper Requirements"; } } else { rs.enabled_alignment_helper = false; utils.write_settings_file(rs); } hid_handler.log << std::format("Alignment helper toggled: {}", rs.enabled_alignment_helper).c_str() << std::endl; } ImGui::PopStyleColor(pushed_styles); if (rs.enabled_alignment_helper) { ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorScreenPos().y - g_Dpi.F(3)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.6f, 1, 1)); ImGui::Text(LSTR(StringID::EYETRACKING_SEE_GUIDE_LINK).c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemClicked()) { rs.pending_popup = "Alignment Helper Guide"; } } } } ImGui::Spacing(); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); } void UI::show_et_advanced_settings(UI_Run_State& rs) { auto windowSize = ImGui::GetWindowSize(); ImGui::BeginTable("TogglesTable", 1); ImGui::TableSetupColumn("TogglesCol", ImGuiTableColumnFlags_WidthFixed, windowSize.x); for (int i = 0; i < et_adv_setting_toggles.size(); i++) { ET_AdvSettingGroup group = et_adv_setting_toggles[i]; if (i % 1 == 0) { ImGui::TableNextRow(); } ImGui::TableNextColumn(); if (i != 0) { ImGui::Separator(); ImGui::Spacing(); } for (int t = 0; t < group.Toggles.size(); t++) { auto toggle = group.Toggles[t]; bool val = toggle.Get(); ImGui::PushID(toggle.ID.c_str()); ImGui::SetWindowFontScale(UIConstants.et_checkbox_Scale); pushed_styles = PushCheckboxStyle(); ImGui::Checkbox(("##" + toggle.ID).c_str(), &val); ImGui::PopStyleColor(pushed_styles); ImGui::SetWindowFontScale(1); ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + g_Dpi.F(3)); ImGui::Text(toggle.ID.c_str()); ImGui::PopID(); if (toggle.Get() != val) { toggle.Set(val); } ImGui::SameLine(); } if (i == 0) { ImGui::SameLine(); pushed_styles = PushButtonStyle(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + g_Dpi.F(3)); if (!rs.et_require_steamvr_presence) { ImGui::BeginDisabled(); } auto textLen = ImGui::CalcTextSize(LSTR(StringID::EYETRACKING_APPS_LIST_BTN).c_str()).x; ImGui::SetCursorPosX(windowSize.x - textLen - g_Dpi.F(20)); if (ImGui::Button(LSTR(StringID::EYETRACKING_APPS_LIST_BTN).c_str(), ImVec2(textLen + g_Dpi.F(10), 0))) { steamvrWatch.UpdateRunningApps(); rs.et_current_apps_section = ET_AppsSection::Running; rs.pending_popup = "Select Running Apps"; } ImGui::PopStyleColor(pushed_styles); if (!rs.et_require_steamvr_presence) { ImGui::EndDisabled(); } ImGui::SetCursorPosY(ImGui::GetCursorPosY() - g_Dpi.F(3)); } ImGui::Spacing(); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); ImGui::TextWrapped(group.Label.c_str()); ImGui::PopStyleColor(); } ImGui::EndTable(); // Dropdown-style advanced settings if (et_adv_setting_dropdowns.size() > 0) { ImGui::Separator(); ImGui::Spacing(); ImGui::BeginTable("DropdownsTable", 1); ImGui::TableSetupColumn("DropdownCol", ImGuiTableColumnFlags_WidthFixed, windowSize.x); for (int i = 0; i < et_adv_setting_dropdowns.size(); i++) { ImGui::TableNextRow(); ImGui::TableNextColumn(); auto dropdown = et_adv_setting_dropdowns[i]; int index = dropdown.Get(); if (index < 0 || index >= dropdown.Options.size()) { index = 0; } std::string currentLabel = dropdown.Options[index]; ImGui::Text(dropdown.Label.c_str()); ImGui::Spacing(); ImGui::PushID(dropdown.Label.c_str()); ImGui::SetNextItemWidth(g_Dpi.F(150)); pushed_styles = PushDropdownStyle(); if (ImGui::BeginCombo(("##" + dropdown.Desc).c_str(), currentLabel.c_str())) { for (int opt = 0; opt < dropdown.Options.size(); opt++) { bool isSelected = (opt == index); if (ImGui::Selectable(dropdown.Options[opt].c_str(), isSelected)) { dropdown.Set(opt); index = opt; } if (isSelected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } ImGui::PopStyleColor(pushed_styles); ImGui::PopID(); ImGui::SameLine(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() - g_Dpi.F(9)); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); ImGui::TextWrapped(dropdown.Desc.c_str()); ImGui::PopStyleColor(); } ImGui::EndTable(); } ImGui::BeginTable("InputsTable", 1); ImGui::TableSetupColumn("InputCol", ImGuiTableColumnFlags_WidthFixed, windowSize.x); for (int i = 0; i < et_adv_setting_inputs.size(); i++) { if (i % 1 == 0) { ImGui::TableNextRow(); } ImGui::TableNextColumn(); ET_AdvSettingInput group = et_adv_setting_inputs[i]; std::string val = group.Get(); ImGui::Separator(); ImGui::Spacing(); ImGui::Text(group.Label.c_str()); ImGui::Spacing(); ImGui::PushID(group.Label.c_str()); ImGui::SetNextItemWidth(g_Dpi.F(group.FieldWidth)); pushed_styles = PushTextInputStyle(); if (ImGui::InputText("", val.data(), 256)) { group.Set(val); } ImGui::PopStyleColor(pushed_styles); ImGui::SameLine(); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); ImGui::TextWrapped(group.Desc.c_str()); ImGui::PopStyleColor(); ImGui::PopID(); if (group.Get() != val) { group.Set(val); } } ImGui::EndTable(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); std::string disclaimer = LSTR(StringID::EYETRACKING_ADV_SETTING_CHANGES); auto textSize = ImGui::CalcTextSize(disclaimer.c_str()); ImGui::SetCursorPosX((windowSize.x - textSize.x) * 0.5f); ImGui::TextWrapped(disclaimer.c_str()); } bool UI::show_refresh_rate_buttons(UI_Run_State& rs) { bool retval = false; Refresh_Rate new_refresh_rate = rs.current_refresh_rate; ImGuiStyle& style = ImGui::GetStyle(); ImVec2 control_vect = ImGui::GetCursorPos(); control_vect.x += g_Dpi.F(UIConstants.control_padding); if (changing_refresh.load()) { float window_edge = (float)g_Dpi.F(window_width) - g_Dpi.F(2) * style.FramePadding.x; auto progress_offset = std::fmax(ImGui::CalcTextSize(LSTR(StringID::STATUS_CHANGING_REFRESH_RATE).c_str()).x + g_Dpi.F(15), control_vect.x); ImGui::SetCursorPosY(control_vect.y + g_Dpi.F(UIConstants.control_thickness)); ImGui::Text(LSTR(StringID::STATUS_CHANGING_REFRESH_RATE).c_str()); ImGui::SetCursorPos(ImVec2(progress_offset, control_vect.y)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, g_Dpi.F(UIConstants.control_thickness))); pushed_styles = PushProgressBarStyle(); ImGui::ProgressBar((float)refresh_progress.load() / 100.0f, ImVec2(window_edge - progress_offset, 0)); ImGui::PopStyleColor(pushed_styles); ImGui::PopStyleVar(); } else { auto textLen = ImGui::CalcTextSize(LSTR(StringID::LABEL_DISPLAY_REFRESH_RATE).c_str()).x; ImGui::SetCursorPosY(control_vect.y + g_Dpi.F(UIConstants.control_thickness)); ImGui::Text(LSTR(StringID::LABEL_DISPLAY_REFRESH_RATE).c_str()); ImVec2 control_offset = ImVec2(textLen + g_Dpi.F(64), control_vect.y); ImGui::SetCursorPos(control_offset); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, g_Dpi.F(UIConstants.control_thickness))); if (refresh_rate_button(LSTR(StringID::REFRESH_75HZ).c_str(), rs.current_refresh_rate == Refresh_Rate::_75hz, rs.valid_path, g_Dpi.Vec2(100, 0))) { new_refresh_rate = Refresh_Rate::_75hz; retval = true; } ImGui::SameLine(); if (refresh_rate_button(LSTR(StringID::REFRESH_90HZ).c_str(), rs.current_refresh_rate == Refresh_Rate::_90hz, rs.valid_path, g_Dpi.Vec2(100, 0))) { new_refresh_rate = Refresh_Rate::_90hz; retval = true; } if (enable_72hz_option()) { ImGui::SameLine(); if (refresh_rate_button(LSTR(StringID::REFRESH_72HZ).c_str(), rs.current_refresh_rate == Refresh_Rate::_72hz, rs.valid_path, g_Dpi.Vec2(100, 0))) { new_refresh_rate = Refresh_Rate::_72hz; retval = true; } } ImGui::PopStyleVar(); rs.current_refresh_rate = new_refresh_rate; } return retval; } // Calls made here only run a single time per unique hmd the utility finds void UI::first_time_headset(UI_Run_State& rs) { utils.mic_volume_fix(rs); // Notify about new hardware std::wstring desc = utils.get_device_type() == "BS2E" && !rs.installed_et_driver ? LWSTR(StringID::TOAST_NEW_DEVICE_ET_MESSAGE).c_str() : LWSTR(StringID::TOAST_NEW_DEVICE_MESSAGE).c_str(); toastHandler.show_notification(desc, (L"\U0001F389 " + LWSTR(StringID::TOAST_NEW_DEVICE_CONGRATS)).c_str(), [this]() { bring_foreground(); }); } bool UI::has_opened_eyetracking() { std::filesystem::path et_settings = std::filesystem::absolute(std::filesystem::path("eyetracking\\ETClient\\eyetracking_settings.json")); bool valid_file = std::filesystem::exists(et_settings) && std::filesystem::is_regular_file(et_settings); if (valid_file) { return true; } return false; } void UI::update_models(std::vector newUserTrainedModels) { if (!rs.rebuilding_Models) { rs.rebuilding_Models = true; modelManager.RebuildModelList(newUserTrainedModels); rs.et_models.clear(); bool hasTrainingModel = false; auto modelsList = modelManager.MasterModelList(); for (ETModel model : modelsList) { if (((Model_Types)model.Type == Model_Types::Legacy || (Model_Types)model.Type == Model_Types::Local) && (rs.active_models_filter == Models_Filter::All || rs.active_models_filter == Models_Filter::OfflineModels)) { rs.et_models.push_back(model); } if ((Model_Types)model.Type == Model_Types::Remote && (rs.active_models_filter == Models_Filter::RemoteModels || rs.active_models_filter == Models_Filter::All)) { rs.et_models.push_back(model); } // HACK HACK HACK // Models in training have a percentage indicator in their names. Look for unique characters // Server *could* return status of pending models apart from baking into names. if (model.Name.find(L"(") != std::string::npos && model.Name.find(L")") != std::string::npos && model.Name.find(L"%")) { hasTrainingModel = true; rs.foundModelInProgress = true; } } // No models were found in training after discovering one // Assume the model has finished if (!hasTrainingModel && rs.foundModelInProgress) { rs.foundModelInProgress = false; // Turn off loop flag in preferences rs.expecting_new_model = false; utils.write_settings_file(rs); } rs.rebuilding_Models = false; } } static bool et_selected_apps_contains(const SteamVRWatch::RunningApp& app, const std::vector& apps) { for (const auto& selected : apps) { if (!app.processName.empty() && !selected.processName.empty() && _wcsicmp(app.processName.c_str(), selected.processName.c_str()) == 0) { return true; } } return false; } static float calc_app_list_child_height(size_t entryCount) { if (entryCount == 0) { return g_Dpi.F(40); } float rowHeight = (ImGui::GetTextLineHeight() * 2.0f) + g_Dpi.F(20); float desired = rowHeight * (float)entryCount; float maxHeight = g_Dpi.F(340); return desired < maxHeight ? desired : maxHeight; } static bool render_app_list_entry(int pos, const SteamVRWatch::RunningApp& app, const char* buttonLabel, float buttonWidth) { std::string title = utils.string_wide_to_narrow(app.windowTitle); if (title.empty()) { title = "Untitled"; } std::string exe = utils.string_wide_to_narrow(app.processName); ImGui::PushID(pos); bool buttonPressed = false; if (ImGui::BeginTable("app_entry", 2, ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupColumn("info", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("action", ImGuiTableColumnFlags_WidthFixed, buttonWidth); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text(title.c_str()); int pushed_subtext = PushSubTextStyle(); ImGui::Text(exe.c_str()); ImGui::PopStyleColor(pushed_subtext); ImGui::TableNextColumn(); int pushed_button = PushButtonStyle(); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + g_Dpi.F(7)); if (ImGui::Button(buttonLabel, ImVec2(buttonWidth, 0))) { buttonPressed = true; } ImGui::PopStyleColor(pushed_button); ImGui::EndTable(); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); ImGui::PopID(); return buttonPressed; } void UI::render_popups(UI_Run_State& rs) { float popup_buttonWidth = 75.0f; float popup_spacing = 10.0f; float popup_totalWidth = g_Dpi.F((popup_buttonWidth * 2) + popup_spacing); float popup_window_normal_width = ImGui::GetWindowSize().x - g_Dpi.F(95); float popup_window_large_width = ImGui::GetWindowSize().x - g_Dpi.F(50); ImVec2 center = ImGui::GetMainViewport()->GetCenter(); ImVec2 pivot = ImVec2(0.5f, 0.5f); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, g_Dpi.F(5)); PushPopupStyle(); // Popup modals for firmware prompts ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Beyond Firmware", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::POPUP_MSG_UPDATE_BEYOND_FIRMWARE).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); hid_handler.command_restart_in_bootloader(); rs.launching_bootloader = true; rs.firmware_update_requested = true; } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Reinitialize Cameras", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::POPUP_MSG_REINITIALIZE_CAMERAS).c_str()); ImGui::Spacing(); ImGui::Text(LSTR(StringID::POPUP_CAM_ADMIN_REQUIREMENT).c_str()); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), ImVec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); // Restart with elevated priviledges for uninstalling the camera. ShellExecute(NULL, L"runas", L"BeyondHID.exe", L"-restartCamera", NULL, SW_SHOWNORMAL); ui.alive.store(false); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), ImVec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("Eyetracking Firmware", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextWrapped(LSTR(StringID::POPUP_MSG_UPDATE_ET_FIRMWARE).c_str()); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::POPUP_MSG_FLASH_WARN).c_str()); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); utils.start_dfu_update(rs); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("Lighthouse Update", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { std::wstring fileName = std::filesystem::path(rs.lighthouse_restore_path).filename().wstring(); std::string stagedInfo = "Selected file: " + utils.string_wide_to_narrow(fileName) + "\nTarget device: " + hid_handler.tundra_serial; ImGui::TextWrapped(LSTR(StringID::POPUP_MSG_UPDATE_LIGHTHOUSE_CONFIG).c_str()); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::POPUP_MSG_UPDATE_VERIFY_CONFIG).c_str()); ImGui::Spacing(); ImGui::Text(stagedInfo.c_str()); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); utils.upload_lighthouse_config(rs); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("Memory Update", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextWrapped(LSTR(StringID::POPUP_MSG_UPDATE_MEMORY_CONFIG).c_str()); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::POPUP_MSG_UPDATE_VERIFY_CONFIG).c_str()); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); saving.store(true); if (!utils.restore_mem_config(hid_handler, rs.memory_restore_path)) { MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_FAILED_WRITE_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } else { rs.pending_popup = "Reconnect Device"; }; } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Restart SteamVR", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::POPUP_MSG_RESTART_STEAMVR).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); centered(g_Dpi.F(120)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(120, 0))) { refresh_progress.store(0); ipd_progress.store(0); set_steamvr_settings.store(false); ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Reconnect Device", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::POPUP_MSG_RECONNECT_DEVICE).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); centered(g_Dpi.F(120)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("IPD Change", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { { std::string msg = LSTR(StringID::POPUP_MSG_IPD_CHANGE); size_t pos = msg.find("%d"); if (pos != std::string::npos) { msg.replace(pos, 2, std::to_string(rs.adjusted_ipd)); } ImGui::Text(msg.c_str()); } ImGui::Spacing(); if (rs.has_eyetracking) { ImGui::Text(LSTR(StringID::POPUP_MSG_IPD_CHANGE_WITH_ET).c_str()); } ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { rs.applied_ipd = rs.adjusted_ipd; utils.write_settings_file(rs); utils.update_ipd(rs); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { rs.adjusted_ipd = rs.applied_ipd; ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Ignore Setting", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::POPUP_MSG_IGNORE_SETTING).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { if (std::find(rs.ignored_steamvr_warnings.begin(), rs.ignored_steamvr_warnings.end(), static_cast(rs.staged_ignore_setting)) == rs.ignored_steamvr_warnings.end()) { ImGui::CloseCurrentPopup(); rs.ignored_steamvr_warnings.push_back(static_cast(rs.staged_ignore_setting)); utils.write_settings_file(rs); // Save to json, including the "ignored_steamvr_warnings" array rs.checked_steamvr_settings = false; } } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Proximity Change", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { { std::string msg = LSTR(StringID::POPUP_MSG_SAVE_PROXIMITY_OFFSET); size_t pos = msg.find("%d"); if (pos != std::string::npos) { msg.replace(pos, 2, std::to_string(rs.prox_offset)); } ImGui::Text(msg.c_str()); } ImGui::Spacing(); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); rs.current_page = Page_Type::Home; saving.store(true); // Write current config out with current GUI options. if (!utils.update_config(hid_handler, rs)) { MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_FAILED_WRITE_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); }; // Save GUI settings. utils.write_settings_file(rs); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Eyetracking Update Failed", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::POPUP_MSG_ET_UPDATE_FAILED).c_str()); ImGui::Spacing(); ImGui::Text(LSTR(StringID::POPUP_MSG_ET_UPDATE_ATTEMPTS).c_str()); ImGui::SameLine(); pushed_styles = PushButtonStyle(); if (ImGui::Button("support@bigscreenvr.com")) { ::ShellExecute(NULL, NULL, L"mailto:support@bigscreenvr.com", NULL, NULL, 0); } ImGui::PopStyleColor(pushed_styles); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); centered(g_Dpi.F(120)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(120, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Idle Sleep Confirm", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::POPUP_MSG_IDLE_SLEEP_CONFIRM).c_str()); ImGui::Spacing(); ImGui::Text(LSTR(StringID::POPUP_MSG_REBOOT_BEYOND).c_str()); ImGui::Separator(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, 0)); ImGui::PopStyleVar(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); rs.current_page = Page_Type::Home; saving.store(true); // Write current config out with VXR toggle. if (!utils.update_config(hid_handler, rs)) { MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_FAILED_WRITE_CONFIG).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } else { reboot_after_save.store(true); // Save GUI settings. utils.write_settings_file(rs); }; } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { // Revert selection rs.vxr_auto_off = rs.vxr_auto_off ? false : true; ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_large_width, 0)); if (ImGui::BeginPopupModal("Eyetracking Advanced Settings", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { show_et_advanced_settings(rs); ImGui::Spacing(); centered(g_Dpi.F(popup_buttonWidth)); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("Alignment Helper Guide", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { float windowWidth = ImGui::GetWindowSize().x; std::string alignTitle = LSTR(StringID::EYETRACKING_ALIGNMENT_HELPER_GUIDE_TITLE).c_str(); if (!rs.started_gif_animation) { rs.started_gif_animation = true; gif_player.Destroy(); gif_player.Load("images/align.gif"); } if (rs.started_gif_animation) { gif_player.Update(gif_player.TimeDelta()); } if (font_header) { ImGui::PushFont(font_header); ImGui::SetWindowFontScale(UIConstants.et_area_scale); auto textSize = ImGui::CalcTextSize(alignTitle.c_str()); ImGui::SetCursorPosX((windowWidth - (textSize.x)) * 0.5f); ImGui::Text(LSTR(StringID::EYETRACKING_ALIGNMENT_HELPER_GUIDE_TITLE).c_str()); ImGui::SetWindowFontScale(1); ImGui::PopFont(); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_ALIGNMENT_HELPER_GUIDE_TEXT).c_str()); ImGui::Spacing(); ImGui::Spacing(); if (gif_player.GetTexture() != 0) { ImGui::SetCursorPosX((windowWidth - (g_Dpi.F(UIConstants.alignment_gif_size.x))) * 0.5f); ImGui::Image((ImTextureID)(intptr_t)gif_player.GetTexture(), g_Dpi.Vec2(UIConstants.alignment_gif_size.x, UIConstants.alignment_gif_size.y)); } ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { rs.started_gif_animation = false; gif_player.Destroy(); ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_large_width, 0)); if (ImGui::BeginPopupModal("Utility Update Fix", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { float windowWidth = ImGui::GetWindowSize().x; std::string alignTitle = LSTR(StringID::POPUP_TITLE_BAD_UPDATE_FIX); if (!rs.started_gif_animation) { rs.started_gif_animation = true; gif_player.Destroy(); gif_player.Load("images/verify.gif"); } if (rs.started_gif_animation) { gif_player.Update(gif_player.TimeDelta()); } if (font_header) { ImGui::PushFont(font_header); ImGui::SetWindowFontScale(UIConstants.et_area_scale); auto textSize = ImGui::CalcTextSize(alignTitle.c_str()); ImGui::SetCursorPosX((windowWidth - (textSize.x)) * 0.5f); ImGui::Text(alignTitle.c_str()); ImGui::SetWindowFontScale(1); ImGui::PopFont(); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::POPUP_BAD_UPDATE_EX).c_str()); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::POPUP_BAD_UPDATE_FIX).c_str()); ImGui::Spacing(); ImGui::Spacing(); if (gif_player.GetTexture() != 0) { ImGui::SetCursorPosX((windowWidth - (g_Dpi.F(UIConstants.verify_gif_size.x))) * 0.5f); ImGui::Image((ImTextureID)(intptr_t)gif_player.GetTexture(), g_Dpi.Vec2(UIConstants.verify_gif_size.x, UIConstants.verify_gif_size.y)); } ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { rs.started_gif_animation = false; gif_player.Destroy(); ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_large_width, 0)); if (ImGui::BeginPopupModal("Beyond SteamVR Add-On", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { float windowWidth = ImGui::GetWindowSize().x; std::string alignTitle = LSTR(StringID::POPUP_ADDON_TITLE); if (font_header) { ImGui::PushFont(font_header); ImGui::SetWindowFontScale(UIConstants.et_area_scale * 0.9f); auto textSize = ImGui::CalcTextSize(alignTitle.c_str()); ImGui::SetCursorPosX((windowWidth - (textSize.x)) * 0.5f); ImGui::Text(alignTitle.c_str()); ImGui::SetWindowFontScale(1); ImGui::PopFont(); } ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::POPUP_ADDON_INFO).c_str()); ImGui::Spacing(); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { #ifndef _DEBUG if (utils.install_bridge_driver(rs)) { set_steamvr_settings.store(true); } #endif ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_large_width, 0)); if (ImGui::BeginPopupModal("Enroll Confirm", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { if (rs.et_models.size() > 0) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_REDO_ENROLLMENT_WITH_IPD_CHANGE).c_str()); } else { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_ENROLLMENT_DISRUPT_WARNING).c_str()); } ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_CLICK_START_WHEN_READY).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::Text(LSTR(StringID::EYETRACKING_ENROLLMENT_CONSENT_PREFIX).c_str()); if (rs.active_locale != Locales::Jp) { ImGui::SameLine(); } ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.2f, 0.6f, 1, 1)); ImGui::Text(LSTR(StringID::EYETRACKING_ENROLLMENT_PRIVACY_LINK).c_str()); ImGui::PopStyleColor(); if (ImGui::IsItemClicked()) { utils.open_link(UIConstants.agreement_link); } ImGui::Separator(); ImGui::Spacing(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_START).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { if (utils.get_XR_layer_count() > 0) { rs.pending_popup = "OpenXR Layers"; } else { utils.launch_enrollment(rs); } ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("Enable Alignment Helper", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_ALIGNMENT_HELPER_ENABLE_QUESTION).c_str()); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_ALIGNMENT_HELPER_OVERLAY_MESSAGE).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_YES).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { rs.enabled_alignment_helper = true; // Only show the guide one time after enabling if (!rs.seen_alignment_guide) { rs.seen_alignment_guide = true; rs.pending_popup = "Alignment Helper Guide"; } utils.write_settings_file(rs); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_NO).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("Alignment Helper Requirements", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_ALIGNMENT_HELPER_REQUIRES_DFR).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { rs.current_page = Page_Type::DFRHome; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("DFR Disable", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_ALIGNMENT_HELPER_DISABLE_WARNING).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_CONTINUE).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { rs.enabled_alignment_helper = false; rs.et_publish_memmap = false; utils.write_settings_file(rs); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("OpenXR Layers", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_OPENXR_LAYER_CONFLICT_WARNING).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { utils.launch_enrollment(rs); ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("Eye Viewer Warning", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_EYE_VIEWER_LATENCY_WARNING).c_str()); ImGui::Spacing(); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_EYE_VIEWER_CLOSE_RECOMMENDATION).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { rs.et_publish_viewer = true; ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(g_Dpi.Vec2(500, 0), ImGuiCond_Always); if (ImGui::BeginPopupModal("Accuracy Tips", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { for (int i = 0; i < accuracy_tips.size(); i++) { ImGui::Spacing(); ImGui::Bullet(); ImGui::SameLine(); ImGui::TextWrapped(accuracy_tips[i].c_str()); ImGui::Spacing(); ImGui::Separator(); } ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Label Model", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { int headerMax = (int)(ImGui::GetMainViewport()->Size.x / 2); std::string header = (LSTR(StringID::EYETRACKING_RENAME_MODEL_LABEL) + " ").c_str() + utils.et_format_model_name(rs, rs.et_selected_model, false); std::string displayText = header; auto textSize = ImGui::CalcTextSize(header.c_str()); if (textSize.x > headerMax) { int len = header.size(); while (len > 0) { std::string final = header.substr(0, len) + "..."; if (ImGui::CalcTextSize(final.c_str()).x <= headerMax) { displayText = final; break; } len--; } } ImGui::Text(displayText.c_str()); ImGui::Spacing(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; pushed_styles = PushTextInputStyle(); ImGui::SetCursorPosX(offsetX - g_Dpi.F(31)); std::string temp_name = rs.pending_edit_model; temp_name.resize(50); if (ImGui::InputText("##ModelName", temp_name.data(), temp_name.size())) { temp_name.resize(strlen(temp_name.c_str())); rs.pending_edit_model = temp_name; } ImGui::PopStyleColor(pushed_styles); ImGui::Separator(); ImGui::SetItemDefaultFocus(); ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0)) && rs.pending_edit_model.size() > 0) { std::string modelName = utils.et_format_model_name(rs, rs.et_selected_model, true); rs.et_labeled_models[modelName] = rs.pending_edit_model; utils.write_settings_file(rs); rs.pending_edit_model = ""; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_CANCEL).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Remove Model", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { int headerMax = (int)(ImGui::GetMainViewport()->Size.x / 2); std::string header = LSTR(StringID::EYETRACKING_REMOVE_MODEL_LABEL) + " " + rs.pending_edit_model; std::string displayText = header; auto textSize = ImGui::CalcTextSize(header.c_str()); if (textSize.x > headerMax) { int len = header.size(); while (len > 0) { std::string final = header.substr(0, len) + "..."; if (ImGui::CalcTextSize(final.c_str()).x <= headerMax) { displayText = final; break; } len--; } } ImGui::Text((displayText + "?").c_str()); ImGui::Spacing(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::Text(LSTR(StringID::EYETRACKING_REMOVE_MODEL_LIST).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_YES).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0)) && rs.pending_edit_model.size() > 0) { std::string modelName = utils.et_format_model_name(rs, rs.et_selected_model, true); if (std::find(rs.et_removed_models.begin(), rs.et_removed_models.end(), modelName) == rs.et_removed_models.end()) { // Rebuild models list and unselect the current model rs.et_removed_models.push_back(modelName); ui.update_models(userManager.UserTrainedModels()); rs.et_selected_model = ""; rs.et_enabled = false; utils.write_settings_file(rs); } rs.pending_edit_model = ""; ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_NO).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("Model Training", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::EYETRACKING_MODEL_TRAINING_IN_PROGRESS).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("DFR Settings Success", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::EYETRACKING_DFR_SETTINGS_APPLIED_RESTART).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("DFR Settings Failure", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::EYETRACKING_QUAD_VIEWS_SETTINGS_WRITE_FAILED).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::BUTTON_OK).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); if (ImGui::BeginPopupModal("DFR Confirm", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { if (!rs.dfr_settings_prompted) { ImGui::Text(LSTR(StringID::EYETRACKING_DFR_APPLY_RECOMMENDED_SETTINGS_QUESTION).c_str()); ImGui::Spacing(); ImGui::Text(LSTR(StringID::EYETRACKING_DFR_OVERWRITE_USER_SETTINGS_WARNING).c_str()); } else { ImGui::Text(LSTR(StringID::EYETRACKING_DFR_APPLY_CHANGE_QUESTION).c_str()); ImGui::Spacing(); ImGui::Text(LSTR(StringID::EYETRACKING_DFR_OVERWRITE_USER_SETTINGS_WARNING).c_str()); } ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_YES).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { if (utils.apply_quad_view_settings(rs)) { rs.pending_popup = "DFR Settings Success"; // Store flag so the popup won't show again rs.dfr_settings_prompted = true; utils.write_settings_file(rs); } else { rs.pending_popup = "DFR Settings Failure"; } ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_NO).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { // Store flag so the popup won't show again rs.dfr_settings_prompted = true; utils.write_settings_file(rs); ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_large_width, 0)); if (ImGui::BeginPopupModal("DFR Apps", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextWrapped(LSTR(StringID::EYETRACKING_DFR_POPULAR_APPS_LIST_DESC).c_str()); ImGui::TextWrapped(LSTR(StringID::EYETRACKING_DFR_QUAD_VIEWS_NOT_REQUIRED_NOTE).c_str()); ImGui::Spacing(); ImGui::Separator(); ImGui::SetItemDefaultFocus(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; for (int i = 0; i < DFR_apps.size(); i++) { DFR_App d_app = DFR_apps[i]; ImGui::Separator(); ImGui::Spacing(); ImGui::Text(d_app.title.c_str()); ImGui::PushStyleColor(ImGuiCol_Text, UIConstants.sub_text); ImGui::TextWrapped(d_app.desc.c_str()); ImGui::PopStyleColor(); ImGui::Spacing(); } ImGui::Separator(); ImGui::Spacing(); ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_CLOSE).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_normal_width, 0)); if (ImGui::BeginPopupModal("Quad-View Settings", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::SetItemDefaultFocus(); DFR_quad_settings(rs, popup_buttonWidth); ImGui::Separator(); ImGui::Spacing(); float windowWidth = ImGui::GetWindowSize().x; float offsetX = (windowWidth - (popup_totalWidth / 2)) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_CLOSE).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::SetNextWindowPos(center, ImGuiCond_Always, pivot); ImGui::SetNextWindowSize(ImVec2(popup_window_large_width, 0)); if (ImGui::BeginPopupModal("Select Running Apps", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { const float selectButtonWidth = ImGui::CalcTextSize(LSTR(StringID::BUTTON_SELECT).c_str()).x + g_Dpi.F(20); const float removeButtonWidth = ImGui::CalcTextSize(LSTR(StringID::BUTTON_REMOVE).c_str()).x + g_Dpi.F(20); const auto runningApps = steamvrWatch.GetRunningApps(); size_t visibleRunningCount = 0; for (const auto& app : runningApps) { if (!et_selected_apps_contains(app, rs.et_selected_apps)) { visibleRunningCount++; } } centered_textwrapped(LSTR(StringID::EYETRACKING_APPS_LIST_DESC).c_str()); ImGui::Spacing(); ImGui::Spacing(); float windowWidth = ImGui::GetWindowSize().x; float sectionButtonWidth = (windowWidth - g_Dpi.F(20)) / 2.0f; centered(sectionButtonWidth * 2.0f); for (int i = 0; i < 2; i++) { if (i > 0) { ImGui::SameLine(0, 0); } const AppListTab& tab = et_app_list_tabs[i]; pushed_styles = rs.et_current_apps_section == tab.section ? PushButtonCategoryStyleHighlighted() : PushButtonCategoryStyle(); if (ImGui::Button(tab.label, ImVec2(sectionButtonWidth, g_Dpi.F(35)))) { rs.et_current_apps_section = tab.section; } ImGui::PopStyleColor(pushed_styles); } const bool showRunning = rs.et_current_apps_section == ET_AppsSection::Running; const size_t listEntryCount = 1 + (showRunning ? visibleRunningCount : rs.et_selected_apps.size()); if (ImGui::BeginChild("AppsList", ImVec2(0, calc_app_list_child_height(listEntryCount)), true)) { if (showRunning) { if (visibleRunningCount == 0) { int pushed_subtext = PushSubTextStyle(); ImGui::Text(LSTR(StringID::EYETRACKING_APPS_NOT_AVAIL).c_str()); ImGui::PopStyleColor(pushed_subtext); } else { int list_pos = 0; for (const auto app : runningApps) { if (et_selected_apps_contains(app, rs.et_selected_apps)) { continue; } list_pos++; if (render_app_list_entry(list_pos, app, LSTR(StringID::BUTTON_SELECT).c_str(), selectButtonWidth)) { rs.et_selected_apps.push_back(app); utils.write_settings_file(rs); } } } } else { if (rs.et_selected_apps.empty()) { int pushed_subtext = PushSubTextStyle(); ImGui::Text(LSTR(StringID::EYETRACKING_APPS_NONE_SELECTED).c_str()); ImGui::PopStyleColor(pushed_subtext); } else { for (size_t i = 0; i < rs.et_selected_apps.size();) { const auto app = rs.et_selected_apps[i]; if (render_app_list_entry(i, app, LSTR(StringID::BUTTON_REMOVE).c_str(), removeButtonWidth)) { rs.et_selected_apps.erase(rs.et_selected_apps.begin() + i); utils.write_settings_file(rs); } else { i++; } } } } } ImGui::EndChild(); ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); float offsetX = (windowWidth - popup_totalWidth) * 0.5f; ImGui::SetCursorPosX(offsetX); pushed_styles = PushButtonStyle(); if (ImGui::Button(LSTR(StringID::EYETRACKING_BUTTON_CLOSE).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { ImGui::CloseCurrentPopup(); } ImGui::SameLine(); if (ImGui::Button(LSTR(StringID::BUTTON_REFRESH).c_str(), g_Dpi.Vec2(popup_buttonWidth, 0))) { steamvrWatch.UpdateRunningApps(); } ImGui::PopStyleColor(pushed_styles); ImGui::EndPopup(); } ImGui::PopStyleColor(1); ImGui::PopStyleVar(1); } /// void UI::run() { bool settings_exists = std::filesystem::exists(rs.settings_path.c_str()) && std::filesystem::is_regular_file(rs.settings_path.c_str()); bool applied_initial_settings = false; bool et_error_shown = false; if (settings_exists) { // If it does exist, read settings in from json. if (!utils.read_settings_file(rs)) { MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_FAILED_READ_SETTINGS).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } } else { // If it does not exist, create new settings json with defaults. utils.write_settings_file(rs); } // Eyetracking user callbacks userManager.OnAuthenticationResponse = [](int result) { rs.auth_status = (Auth_Code)result; // Switch models to downloaded list if the user can't authenticate if (rs.auth_status != Auth_Code::Success) { rs.active_models_filter = Models_Filter::OfflineModels; ui.update_models({}); ModelCheck exists = modelManager.ModelExists(std::wstring(rs.et_selected_model.begin(), rs.et_selected_model.end())); if (!exists.LegacyExists && !exists.LocalExists) { rs.et_selected_model = ""; } } }; userManager.OnUserIdChanged = [](std::string userId) { if (!userId.empty()) { bool changedToken = rs.et_user_token != userManager.TestingToken(); rs.et_user_token = userManager.TestingToken(); if (changedToken) { utils.write_settings_file(rs); } hid_handler.log << "User ID changed to: " << userId.c_str() << std::endl; } }; userManager.OnUserTrainedModelsChanged = [](std::vector newUserTrainedModels) { ui.update_models(newUserTrainedModels); // If a model is in the process of training, start the checker loop if (!rs.expecting_new_model) { for (std::string t_model : newUserTrainedModels) { if (t_model.find("(") != std::string::npos && t_model.find(")") != std::string::npos && t_model.find("%") != std::string::npos) { rs.model_lifetime_check_timer = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); rs.expecting_new_model = true; } } } // The user has a remote model selected if (!rs.et_selected_model.empty()) { auto models_List = modelManager.MasterModelList(); for (ETModel model : models_List) { if ((Model_Types)model.Type == Model_Types::Remote && utils.string_wide_to_narrow(model.Name) == rs.et_selected_model) { modelManager.OnModelSelected(model); break; } } } }; modelManager.OnModelReady = [](std::string modelPath, bool rebuildList) { if (rebuildList) { ui.update_models(userManager.UserTrainedModels()); } rs.et_model_path = modelPath; }; // Update local models ui.update_models({}); // Set user token from settings if (!rs.et_user_token.empty()) { userManager.SetTestingToken(rs.et_user_token); } // Model checker loop counters rs.model_check_timer = rs.model_lifetime_check_timer = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); // Load if the selected model targets a model on local disk if (!rs.et_selected_model.empty() && rs.et_selected_model.find("s3://") == std::string::npos) { bool model_found = false; auto models_List = modelManager.MasterModelList(); for (ETModel model : models_List) { if ((Model_Types)model.Type != Model_Types::Remote && utils.string_wide_to_narrow(model.Name) == rs.et_selected_model) { model_found = true; modelManager.OnModelSelected(model); break; } } if (!model_found) { rs.et_model_path = rs.et_selected_model; } } // If no active language is set, assign one using system's display language if (rs.active_locale == Locales::None) { utils.find_system_locale(rs); // Save assignment utils.write_settings_file(rs); } Localization::SetLanguage(rs.active_locale); // Set DPI default to avoid startup font rebuild rs.last_window_scale = update_dpi(); hid_handler.log << "Updated window scale: " << rs.last_window_scale.x << " x " << rs.last_window_scale.y << std::endl; // Make sure our path to lighthouse console is valid. rs.exe_path = rs.steamvr_path + L"\\tools\\lighthouse\\bin\\win64\\lighthouse_console.exe"; rs.valid_path = std::filesystem::exists(rs.exe_path.c_str()) && std::filesystem::is_regular_file(rs.exe_path.c_str()); rs.firmware_contents_exists = std::filesystem::exists(rs.firmware_contents_path.c_str()) && std::filesystem::is_regular_file(rs.firmware_contents_path.c_str()); if (rs.firmware_contents_exists) { // Load the latest firmware contents folder and we can later use it to check if an update is required. if (!rs.fw.load_firmware_file(rs.firmware_contents_path)) { MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_FAILED_READ_FIRMWARE).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } } rs.dfu_firmware_exists = std::filesystem::exists(rs.dfu_firmware_path.c_str()) && std::filesystem::is_regular_file(rs.dfu_firmware_path.c_str()); if (rs.dfu_firmware_exists) { // Load the latest firmware contents folder and we can later use it to check if an update is required. if (!rs.fw.get_dfu_version(rs.dfu_firmware_path)) { MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_FAILED_READ_DFU_FIRMWARE).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); } } // -1 is used as the "don't do anything" value. Since 0 is a valid value to send // to the headset to change refresh rates. set_current_refresh.store(-1); //*** Main loop. while (ui.alive.load()) { // Handle starting the eyetracking runtime if the device supports it if (rs.has_eyetracking) { eyetracking_runtime.Configure(rs); if (!eyetracking_runtime.LoopRunning()) { eyetracking_runtime.Start(); } // Consume DML fallback diagnostic flag (runtime already falls back to CPU in-session when needed). eyetracking_runtime.ConsumeDmlFallbackFlag(); // Any eyetracking errors to surface once if (rs.et_enabled) { std::string et_error = eyetracking_runtime.GetLastError(); if (!et_error.empty() && !et_error_shown) { et_error_shown = true; std::wstring wmsg(et_error.begin(), et_error.end()); MessageBox(glfwGetWin32Window(window), wmsg.c_str(), L"Eyetracking Error", MB_OK); } } else { et_error_shown = false; } } // If the eyetracking runtime is active but the device does not support, it should stop if (!rs.has_eyetracking && eyetracking_runtime.LoopRunning()) { eyetracking_runtime.Stop(); } // Poll input. glfwPollEvents(); // Get device generation deviceType = utils.get_device_type(); if (!deviceType.empty()) { rs.ipd_supported = deviceType == "BS2E" || deviceType == "BS2"; rs.has_eyetracking = deviceType == "BS2E"; } // Don't throttle for any active headset changes nor while in bootloader/crash handler mode bool utilityWorking = changing_refresh.load() || changing_ipd.load() || uploading_lighthouse_config.load() || saving.load() || Beyond_Device_State::Bootloader == hid_handler.device_state.load() || Beyond_Device_State::CrashHandler == hid_handler.device_state.load(); HWND hwnd = glfwGetWin32Window(window); bool windowIsMinimized = IsIconic(hwnd); // Outside process may have foregrounded the window if (windowClosed && IsWindowVisible(hwnd)) { windowClosed = false; } ui.app_inactive.store((windowClosed || windowIsMinimized) && !utilityWorking); // Notifying about eyetracking if named processes are active if (rs.has_eyetracking && !rs.installed_et_driver) { for (const auto& pair : process_watch.active_processes) { const std::string& name = pair.first; const Process_Status& status = pair.second; if (status.running && !status.shownToast) { process_watch.active_processes[name].shownToast = true; toastHandler.show_notification(LWSTR(StringID::TOAST_ET_SETUP_MESSAGE).c_str(), LWSTR(StringID::TOAST_ET_SETUP_TITLE).c_str(), [this]() { bring_foreground(true); rs.current_page = Page_Type::ETSettings; }); } } } // Handle fetching device information even while the process is in the background // Important that this doesn't run while beginning a firmware update if (Beyond_Device_State::Application == hid_handler.device_state.load() && !rs.firmware_update_requested) { if (hid_handler.firmware_version.empty()) { hid_handler.command_software_version(); } else { // Get serial after finding the firmware version if (hid_handler.hmd_serial == "") { hid_handler.command_hmd_serial_number(); } else { // The first time a headset is connected, save its serial number to settings json if (std::find(rs.hmd_serial_history.begin(), rs.hmd_serial_history.end(), hid_handler.hmd_serial) == rs.hmd_serial_history.end()) { rs.hmd_serial_history.push_back(hid_handler.hmd_serial); utils.write_settings_file(rs); // Save to json -- including the "hmd_serial_history" array // First time seeing this headset, perform first time calls first_time_headset(rs); } // A headset is connected with unknown fan generation. Attempt to find using RPM values if (!finding_fan_gen.load() && rs.hmd_fan_gen.find(hid_handler.hmd_serial) == rs.hmd_fan_gen.end()) { utils.find_fan_generation(rs); } } } } // Skip UI updates when the app is closed to the system tray or minimized // Important to not pause the UI update during possible headset changes if (ui.app_inactive.load() && set_current_refresh.load() == -1) { Sleep(100); // Don't clog the core. continue; } // Flag to throttle the UI framerate if the cursor is not hovering over any elements bool windowIsActive = utilityWorking || ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup) || ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId); // Rebuild fonts from scale changes if (rs.rebuild_fonts) { load_fonts(); // Reset style scaling ImGuiStyle& style = ImGui::GetStyle(); style = ImGuiStyle(); ImGui::StyleColorsDark(); style.ScaleAllSizes(g_Dpi.scale); rs.rebuild_fonts = false; int c_x, c_y; glfwGetWindowPos(window, &c_x, &c_y); glfwSetWindowMonitor(window, NULL, c_x, c_y, window_width * g_Dpi.scale, window_height * g_Dpi.scale, GLFW_DONT_CARE); } // GUI window. ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); ImGui::SetNextWindowPos(g_Dpi.Vec2(0.0f, 0.0f)); ImGui::SetNextWindowSize(g_Dpi.Vec2((float)window_width, (float)window_height)); ImGui::Begin("##nothing", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBackground); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, g_Dpi.F(3)); ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, g_Dpi.F(3)); ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, g_Dpi.F(3)); ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, g_Dpi.F(3)); // Determine warning banner to show rs.active_warning = Warning_State::None; std::vector ig_flags = rs.ignored_utility_warnings; if (rs.utility_failed_update && std::find(ig_flags.begin(), ig_flags.end(), static_cast(Warning_State::PartialUpdate)) == ig_flags.end()) { rs.active_warning = Warning_State::PartialUpdate; } else if (hid_handler.et_camera_state == Beyond_Camera_State::NotFound && std::find(ig_flags.begin(), ig_flags.end(), static_cast(Warning_State::NoCamera)) == ig_flags.end()) { rs.active_warning = Warning_State::NoCamera; } else if (rs.flagged_settings.size() > 0 && std::find(ig_flags.begin(), ig_flags.end(), static_cast(Warning_State::SteamVR)) == ig_flags.end()) { rs.active_warning = Warning_State::SteamVR; } else if (eyetracking_runtime.RuntimeActive() && !eyetracking_runtime.UsingDirectML() && std::find(ig_flags.begin(), ig_flags.end(), static_cast(Warning_State::UsingCPU)) == ig_flags.end()) { rs.active_warning = Warning_State::UsingCPU; } else if (rs.et_enabled && eyetracking_runtime.CameraAccessRevoked() && std::find(ig_flags.begin(), ig_flags.end(), static_cast(Warning_State::CameraRevoked)) == ig_flags.end()) { rs.active_warning = Warning_State::CameraRevoked; } else if (rs.et_enabled && eyetracking_runtime.FrustumLoadFailure() && std::find(ig_flags.begin(), ig_flags.end(), static_cast(Warning_State::NoFrustum)) == ig_flags.end()) { rs.active_warning = Warning_State::NoFrustum; } else if (rs.et_enabled && eyetracking_runtime.ModelLoadFailure() && std::find(ig_flags.begin(), ig_flags.end(), static_cast(Warning_State::ModelFailure)) == ig_flags.end()) { rs.active_warning = Warning_State::ModelFailure; } // Render the warning banner if a valid state exists if (rs.active_warning != Warning_State::None && rs.current_page != Page_Type::VRSettings && Beyond_Device_State::Application == hid_handler.device_state.load()) { Warning_Banner banner_info = warning_banners.at(rs.active_warning); ImVec2 windowSize = ImGui::GetWindowSize(); ImVec2 boxSize(windowSize.x * 0.98f, g_Dpi.F(42)); ImVec2 boxPos((windowSize.x - boxSize.x) * 0.5f, ImGui::GetCursorPosY()); ImVec4 boxColor = ImVec4(0.4f, 0.0f, 0.0f, 1.0f); // Red color ImGui::GetWindowDrawList()->AddRectFilled(boxPos, ImVec2(boxPos.x + boxSize.x, boxPos.y + boxSize.y), ImColor(boxColor)); // Draw red box ImGui::SetCursorScreenPos(ImVec2(boxPos.x + g_Dpi.F(15), boxPos.y)); // Banner text region ImGui::BeginChild("banner", ImVec2(boxSize.x - g_Dpi.F(100), boxSize.y), false, ImGuiWindowFlags_NoInputs); float wrap_width = ImGui::GetContentRegionAvail().x; auto textSize = ImGui::CalcTextSize(banner_info.title.c_str(), nullptr, false, wrap_width); ImVec2 textPose = ImGui::GetCursorPos(); ImGui::SetCursorPosY((boxSize.y / 2) - (textSize.y / 2)); ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0.0f, 0.0f, 1.0f)); ImGui::TextWrapped(banner_info.title.c_str()); ImGui::PopStyleColor(); // Reset text color ImGui::EndChild(); // Red button color style ImVec4 buttonColor = ImVec4(0.55f, 0.0f, 0.0f, 1.0f); ImVec4 buttonHoveredColor = ImVec4(0.7f, 0.0f, 0.0f, 1.0f); ImVec4 buttonActiveColor = ImVec4(0.6f, 0.0f, 0.0f, 1.0f); ImGui::PushStyleColor(ImGuiCol_Button, buttonColor); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoveredColor); ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonActiveColor); ImGui::SameLine(boxPos.x + boxSize.x - g_Dpi.F(20)); ImGui::SetCursorPosY(boxPos.y + g_Dpi.F(2)); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_Dpi.Vec2(0, -8)); if (ImGui::Button("x", g_Dpi.Vec2(15, 15))) { rs.ignored_utility_warnings.push_back((int)rs.active_warning); } ImGui::PopStyleVar(); if (!banner_info.button_text.empty()) { auto btnTextLen = ImGui::CalcTextSize(banner_info.button_text.c_str()).x; ImGui::SameLine(boxPos.x + textSize.x + (btnTextLen / 2) + g_Dpi.F(5)); ImGui::SetCursorPosY(boxPos.y + g_Dpi.F(12)); if (ImGui::Button(banner_info.button_text.c_str())) { if (rs.active_warning == Warning_State::NoCamera) { rs.pending_popup = "Reinitialize Cameras"; } if (rs.active_warning == Warning_State::SteamVR) { rs.current_page = Page_Type::VRSettings; } if (rs.active_warning == Warning_State::PartialUpdate) { rs.pending_popup = "Utility Update Fix"; } } } ImGui::PopStyleColor(3); ImGui::SetCursorPosY(boxPos.y + boxSize.y); } // Render logo else { auto [logo_tex, w, h] = tex_loader.GetPng("images/logo.png"); centered(g_Dpi.F((float)(w / 3.0f - 10))); ImGui::Image((void*)(intptr_t)logo_tex, g_Dpi.Vec2((float)w / 3.0f, (float)h / 3.0f)); } ImGui::Spacing(); ImGui::Separator(); ImGui::Spacing(); glfwSwapInterval(windowIsActive ? 1 : 4); // SteamVR change popup if (refresh_progress.load() >= 100 || ipd_progress.load() >= 100 || set_steamvr_settings.load()) { ImGui::SetItemDefaultFocus(); rs.pending_popup = "Restart SteamVR"; // Set the current refresh live to the HMD. // The config was set earlier, but that doesn't take effect until power cycling. if (set_current_refresh.load() != -1 && Beyond_Device_State::Application == hid_handler.device_state.load()) { hid_handler.command_edid_switch(set_current_refresh.load()); set_current_refresh.store(-1); } } // Check the user's SteamVR settings periodically // Perform the check one time at startup or after every 15 seconds uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); if (!rs.checked_steamvr_settings || now - rs.last_steamvr_check >= 15) { utils.find_flagged_settings(rs); utils.update_et_driver_status(rs); utils.update_bridge_driver_status(rs); rs.last_steamvr_check = now; rs.checked_steamvr_settings = true; } // Validate running utility version every 30 seconds if (now - rs.last_util_ver_check >= 30) { rs.utility_failed_update = utils.check_utility_ver(); rs.last_util_ver_check = now; } // Update DPI scaling every 3 seconds if (now - rs.last_dpi_update >= 3) { ImVec2 window_scale = update_dpi(); if (window_scale.x + window_scale.y != rs.last_window_scale.x + rs.last_window_scale.y) { rs.last_window_scale = window_scale; hid_handler.log << "Updated window scale: " << window_scale.x << " x " << window_scale.y << std::endl; // Queue font rebuild rs.rebuild_fonts = true; } rs.last_dpi_update = now; } // Update loop for fetching new models if (rs.has_eyetracking) { if (rs.expecting_new_model) { // Max time has lapsed and no model was received if (now - rs.model_lifetime_check_timer >= UIConstants.model_check_lifetime) { rs.expecting_new_model = false; utils.write_settings_file(rs); } } if (rs.expecting_new_model || !userManager.ReceivedModelsResponse()) { // Periodically check for new models if (now - rs.model_check_timer >= UIConstants.model_check_interval) { rs.model_check_timer = now; userManager.FetchUserTrainedModels(); } } } // If DFU command isn't processed, timeout if (hid_handler.entering_dfu.load() && now - rs.initiated_dfu_request >= 6) { hid_handler.entering_dfu.store(false); MessageBox(glfwGetWin32Window(window), LWSTR(StringID::ERROR_DFU_MODE_FAILED).c_str(), LWSTR(StringID::POPUP_TITLE_ERROR).c_str(), MB_OK); rs.dfu_update_requested = false; rs.fw.clear_dfu_status(); hid_handler.awaiting_device.store(true); hid_handler.leaving_dfu.store(true); hid_handler.device = NULL; hid_handler.device_state.store(Beyond_Device_State::Disconnected); } // If DFU update progress has not changed in x amount of seconds, consider the update a failure if (rs.fw.get_dfu_status() == DFU_Load_Status::In_Progress && now - rs.fw.last_dfu_change >= 10) { rs.fw.abort_dfu_update(); } // Any calls to show a popup if (!rs.pending_popup.empty()) { ImGui::OpenPopup(rs.pending_popup.c_str()); rs.pending_popup = ""; } // No device if (hid_handler.awaiting_device.load()) { rs.first_time_in_bootloader = true; rs.first_time_in_crashhandler = true; hid_handler.et_camera_state = Beyond_Camera_State::Unknown; hid_handler.et_camera_check_attempt = 0; hid_handler.firmware_version = ""; hid_handler.et_cam_version = ""; hid_handler.usage_timer_status = Usage_Timer_State::Done; hid_handler.total_power_on = -1; hid_handler.displays_on = -1; hid_handler.longest_displays_on = -1; ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); centered_text(LSTR(StringID::STATUS_AWAITING_DEVICE)); ImGui::Spacing(); ImGui::Separator(); ImGui::Separator(); ImGui::Spacing(); ImGui::PopStyleColor(); centered_text(std::format("{} {}", LSTR(StringID::UTILITY_VERSION), ui.app_version)); ImGui::PopStyleVar(3); ImGui::End(); // Modal if we are intentionally waiting on the bootloader restart if (rs.launching_bootloader) { ImGui::OpenPopup("Restarting"); } if (ImGui::BeginPopupModal("Restarting", NULL, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text(LSTR(StringID::STATUS_RESTARTING_BOOTLOADER).c_str()); ImGui::EndPopup(); } // Render. ImGui::Render(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); Sleep(windowIsActive ? 1 : 100); // Don't clog the core. continue; } if (Beyond_Device_State::Bootloader == hid_handler.device_state.load()) { show_bootloader_menu(rs); } if (Beyond_Device_State::CrashHandler == hid_handler.device_state.load()) { show_crashhandler_menu(rs); } if (Beyond_Device_State::EyetrackingDFU == hid_handler.device_state.load()) { show_dfu_menu(rs); } if (Beyond_Device_State::Application == hid_handler.device_state.load()) { // Primary home page if (rs.current_page == Page_Type::Home) { show_home_page(rs); } // Page for the user to resolve the flagged SteamVR settings if (rs.current_page == Page_Type::VRSettings) { show_steamvr_flagged_settings(rs); if (rs.flagged_settings.size() == 0) { rs.current_page = Page_Type::Home; } } // Advanced device settings if (rs.current_page == Page_Type::Advanced) { show_advanced_settings(rs); } // Primary home page if (rs.current_page == Page_Type::ETSettings) { show_eyetracking_settings(rs); } if (rs.current_page == Page_Type::DFRHome) { show_DFR_home(rs); } } // Render popups render_popups(rs); ImGui::PopStyleVar(4); ImGui::End(); // Render. ImGui::Render(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); Sleep(windowIsActive ? 1 : 100); // Don't clog the core. } ui.alive.store(false); } /// void UI::shutdown() { ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); eyetracking_runtime.Stop(); ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); }