From d98bf5ccf5653968eda9c8a63dff242291bf20fa Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 16 Feb 2026 21:43:23 +0100 Subject: [PATCH 01/12] Added serialization of traceAlpha and persistenceDecay to session. Fix for https://github.com/ngscopeclient/scopehal-apps/issues/936 --- src/ngscopeclient/MainWindow.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index d5348a38..c1e52454 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -2604,6 +2604,12 @@ bool MainWindow::LoadUIConfiguration(int version, const YAML::Node& node) m_pendingHeight = window["height"].as(); m_softwareResizeRequested = true; } + // Load trace alpha + if(window["traceAlpha"]) + m_traceAlpha = window["traceAlpha"].as(); + // Load persistance s + if(window["persistenceDecay"]) + m_persistenceDecay = window["persistenceDecay"].as(); } //Waveform groups @@ -3294,6 +3300,8 @@ YAML::Node MainWindow::SerializeUIConfiguration() window["height"] = m_height; window["width"] = m_width; window["fullscreen"] = m_fullscreen; + window["traceAlpha"] = m_traceAlpha; + window["persistenceDecay"] = m_persistenceDecay; node["window"] = window; //Waveform areas are hierarchical internally, but written as separate area and group headings From 417bbe5ea2d171d82565f204d9a51f22f37b64e8 Mon Sep 17 00:00:00 2001 From: fredzo Date: Mon, 16 Feb 2026 21:52:20 +0100 Subject: [PATCH 02/12] Fixed console output at startup (--help / --version + invalid argument reporting). Fixes https://github.com/ngscopeclient/scopehal-apps/issues/723 --- src/ngscopeclient/main.cpp | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/ngscopeclient/main.cpp b/src/ngscopeclient/main.cpp index b4b632a6..9a37e08b 100644 --- a/src/ngscopeclient/main.cpp +++ b/src/ngscopeclient/main.cpp @@ -92,6 +92,34 @@ int main(int argc, char* argv[]) //Global settings Severity console_verbosity = Severity::NOTICE; + //Windows needs special console handling! + #ifdef _WIN32 + bool attachConsoleFailed = false; + bool getConsoleWindowFailed = false; + //If we have a parent process console, we were probably run from a powershell/cmd.exe session. + //If we had one, we need to attach to it (since as a Win32 subsystem application we aren't connected by default) + //Failing here indicates we were run from explorer, and thus should not be spawning a console window + //(we just log to the GuiLogSink instead) + if(!AttachConsole(ATTACH_PARENT_PROCESS)) + { + attachConsoleFailed = true; + } + + //Once we've attached to the console (if we had one), make sure we had a window for it + else if(GetConsoleWindow() == NULL) + getConsoleWindowFailed = true; + + //If we get here, we were run from a Windows shell session and should log to that console + else + { + //We're using the existing parent process console. + //Reopen stdio streams so they point to it + freopen("CON", "w", stdout); + freopen("CON", "w", stderr); + freopen("CON", "r", stdin); + } + #endif + string sessionToOpen; vector instrumentConnectionStrings; for(int i=1; i Date: Mon, 16 Feb 2026 21:54:38 +0100 Subject: [PATCH 03/12] Made the application start maximized by default. Added --no-maximize/-nm command line arguments to prevent starting the application maximized. Fixes https://github.com/ngscopeclient/scopehal-apps/issues/749 --- src/ngscopeclient/MainWindow.cpp | 4 ++-- src/ngscopeclient/MainWindow.h | 2 +- src/ngscopeclient/VulkanWindow.cpp | 25 +++++++++++++++++++++++-- src/ngscopeclient/VulkanWindow.h | 2 +- src/ngscopeclient/main.cpp | 9 ++++++++- 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index c1e52454..b846bbea 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -115,8 +115,8 @@ static void MainWindow_OnChangedViewport([[maybe_unused]] ImGuiViewport *vp) #define DBG_SUFFIX "" #endif -MainWindow::MainWindow(shared_ptr queue) - : VulkanWindow("ngscopeclient " NGSCOPECLIENT_VERSION " " DBG_SUFFIX SAN_SUFFIX, queue) +MainWindow::MainWindow(shared_ptr queue, bool noMaximise) + : VulkanWindow("ngscopeclient " NGSCOPECLIENT_VERSION " " DBG_SUFFIX SAN_SUFFIX, queue, noMaximise) , m_showDemo(false) , m_nextWaveformGroup(1) , m_toolbarIconSize(0) diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index 5773afc0..49d0d4fd 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -164,7 +164,7 @@ class DockDialogRequest class MainWindow : public VulkanWindow { public: - MainWindow(std::shared_ptr queue); + MainWindow(std::shared_ptr queue, bool noMaximize); virtual ~MainWindow(); static bool OnMemoryPressureStatic(MemoryPressureLevel level, MemoryPressureType type, size_t requestedSize); diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index b53ec83e..32fe9714 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -57,7 +57,7 @@ void (*ImGui_ImplVulkan_SetWindowSize)(ImGuiViewport* viewport, ImVec2 size); /** @brief Creates a new top level window with the specified title */ -VulkanWindow::VulkanWindow(const string& title, shared_ptr queue) +VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, bool noMaximize) : m_renderQueue(queue) , m_resizeEventPending(false) , m_softwareResizeRequested(false) @@ -105,12 +105,33 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue) glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); //Create the window - m_window = glfwCreateWindow(1280, 720, title.c_str(), nullptr, nullptr); + int wx, wy, ww, wh; + if(noMaximize) + { + m_window = glfwCreateWindow(1280, 720, title.c_str(), nullptr, nullptr); + } + else + { + GLFWmonitor* monitor = glfwGetPrimaryMonitor(); + glfwGetMonitorWorkarea(monitor, &wx, &wy, &ww, &wh); + m_window = glfwCreateWindow(ww, wh, title.c_str(), nullptr, nullptr); + } if(!m_window) { LogError("Window creation failed\n"); abort(); } + if(!noMaximize) + { + int left, top, right, bottom; + glfwGetWindowFrameSize(m_window, &left, &top, &right, &bottom); + glfwSetWindowMonitor(m_window, nullptr, 0, top, ww, wh-top, GLFW_DONT_CARE); + LogTrace("Window frame size: %d %d %d %d\n", top, left, right, bottom); + LogTrace("Workarea origin: %d %d\n", wx, wy); + float xscale, yscale; + glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale); + LogTrace("DPI scale: %f %f\n", xscale, yscale); + } //Create a Vulkan surface for drawing onto VkSurfaceKHR surface; diff --git a/src/ngscopeclient/VulkanWindow.h b/src/ngscopeclient/VulkanWindow.h index 413d4bab..92d35b92 100644 --- a/src/ngscopeclient/VulkanWindow.h +++ b/src/ngscopeclient/VulkanWindow.h @@ -43,7 +43,7 @@ class Texture; class VulkanWindow { public: - VulkanWindow(const std::string& title, std::shared_ptr queue); + VulkanWindow(const std::string& title, std::shared_ptr queue, bool noMaximize); virtual ~VulkanWindow(); GLFWwindow* GetWindow() diff --git a/src/ngscopeclient/main.cpp b/src/ngscopeclient/main.cpp index 9a37e08b..c385e55a 100644 --- a/src/ngscopeclient/main.cpp +++ b/src/ngscopeclient/main.cpp @@ -121,6 +121,7 @@ int main(int argc, char* argv[]) #endif string sessionToOpen; + bool noMaximize = false; vector instrumentConnectionStrings; for(int i=1; i queue(g_vkQueueManager->GetRenderQueue("g_mainWindow.render")); - g_mainWindow = make_unique(queue); + g_mainWindow = make_unique(queue,noMaximize); auto& session = g_mainWindow->GetSession(); From 4b5469604750382d1016619d7ab42033545dd78f Mon Sep 17 00:00:00 2001 From: fredzo Date: Wed, 18 Feb 2026 00:58:46 +0100 Subject: [PATCH 04/12] Added window size and position save to preferences. Added --no-restore command line argument to prevent restoring position annd size. Made PreferenceManager an singleton. Allowed public write access to Preferences in PreferenceManager. --- src/ngscopeclient/MainWindow.cpp | 4 +- src/ngscopeclient/MainWindow.h | 2 +- src/ngscopeclient/PreferenceManager.cpp | 5 ++ src/ngscopeclient/PreferenceManager.h | 6 ++ src/ngscopeclient/PreferenceSchema.cpp | 11 +++ src/ngscopeclient/Session.cpp | 8 +-- src/ngscopeclient/Session.h | 7 +- src/ngscopeclient/VulkanWindow.cpp | 95 +++++++++++++++++++++---- src/ngscopeclient/VulkanWindow.h | 7 +- src/ngscopeclient/main.cpp | 14 +++- 10 files changed, 132 insertions(+), 27 deletions(-) diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index b846bbea..a911edfb 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -115,8 +115,8 @@ static void MainWindow_OnChangedViewport([[maybe_unused]] ImGuiViewport *vp) #define DBG_SUFFIX "" #endif -MainWindow::MainWindow(shared_ptr queue, bool noMaximise) - : VulkanWindow("ngscopeclient " NGSCOPECLIENT_VERSION " " DBG_SUFFIX SAN_SUFFIX, queue, noMaximise) +MainWindow::MainWindow(shared_ptr queue, bool noMaximize, bool noRestore) + : VulkanWindow("ngscopeclient " NGSCOPECLIENT_VERSION " " DBG_SUFFIX SAN_SUFFIX, queue, noMaximize, noRestore) , m_showDemo(false) , m_nextWaveformGroup(1) , m_toolbarIconSize(0) diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index 49d0d4fd..6ed57d17 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -164,7 +164,7 @@ class DockDialogRequest class MainWindow : public VulkanWindow { public: - MainWindow(std::shared_ptr queue, bool noMaximize); + MainWindow(std::shared_ptr queue, bool noMaximize, bool noRestore); virtual ~MainWindow(); static bool OnMemoryPressureStatic(MemoryPressureLevel level, MemoryPressureType type, size_t requestedSize); diff --git a/src/ngscopeclient/PreferenceManager.cpp b/src/ngscopeclient/PreferenceManager.cpp index 4ce02a0d..8ee52fc6 100644 --- a/src/ngscopeclient/PreferenceManager.cpp +++ b/src/ngscopeclient/PreferenceManager.cpp @@ -67,6 +67,11 @@ const Preference& PreferenceManager::GetPreference(const string& path) const return this->m_treeRoot.GetLeaf(path); } +Preference& PreferenceManager::GetPreference(const string& path) +{ + return this->m_treeRoot.GetLeaf(path); +} + void PreferenceManager::DeterminePath() { #ifdef _WIN32 diff --git a/src/ngscopeclient/PreferenceManager.h b/src/ngscopeclient/PreferenceManager.h index ffe220c1..35ac5b8e 100644 --- a/src/ngscopeclient/PreferenceManager.h +++ b/src/ngscopeclient/PreferenceManager.h @@ -65,9 +65,13 @@ class PreferenceManager PreferenceManager& operator=(const PreferenceManager&) = delete; PreferenceManager& operator=(PreferenceManager&&) = default; + // Singleton access + static PreferenceManager& GetPreferences() { return m_instance; }; + public: void SavePreferences(); PreferenceCategory& AllPreferences(); + Preference& GetPreference(const std::string& path); std::string GetConfigDirectory() { return m_configDir; } @@ -103,6 +107,8 @@ class PreferenceManager PreferenceCategory m_treeRoot; std::string m_filePath; std::string m_configDir; + // Singleton instance + static PreferenceManager m_instance; }; #endif // PreferenceManager_h diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index 3de6a10f..fc91e45d 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -31,6 +31,8 @@ #include "PreferenceTypes.h" #include "ngscopeclient.h" +PreferenceManager PreferenceManager::m_instance; + void PreferenceManager::InitializeDefaults() { auto& appearance = this->m_treeRoot.AddCategory("Appearance"); @@ -483,6 +485,15 @@ void PreferenceManager::InitializeDefaults() .EnumValue("Multi window", VIEWPORT_ENABLE) .EnumValue("Single window", VIEWPORT_DISABLE) ); + auto& windowStartup = appearance.AddCategory("Startup"); + windowStartup.AddPreference(Preference::Bool("startup_fullscreen", false).Invisible()); + windowStartup.AddPreference(Preference::Bool("startup_maximized", false).Invisible()); + windowStartup.AddPreference(Preference::Int ("startup_pos_x", 0).Invisible()); + windowStartup.AddPreference(Preference::Int ("startup_pos_y", 0).Invisible()); + windowStartup.AddPreference(Preference::Int ("startup_size_width", 0).Invisible()); + windowStartup.AddPreference(Preference::Int ("startup_size_heigth", 0).Invisible()); + windowStartup.AddPreference(Preference::Int ("monitor_width", 0).Invisible()); + windowStartup.AddPreference(Preference::Int ("monitor_heigth", 0).Invisible()); auto& drivers = this->m_treeRoot.AddCategory("Drivers"); auto& dgeneral = drivers.AddCategory("General"); diff --git a/src/ngscopeclient/Session.cpp b/src/ngscopeclient/Session.cpp index 1687e042..2b77648b 100644 --- a/src/ngscopeclient/Session.cpp +++ b/src/ngscopeclient/Session.cpp @@ -2854,7 +2854,7 @@ void Session::ApplyPreferences(shared_ptr scope) auto lecroy = dynamic_pointer_cast(scope); if(lecroy) { - if(m_preferences.GetBool("Drivers.Teledyne LeCroy.force_16bit")) + if(GetPreferences().GetBool("Drivers.Teledyne LeCroy.force_16bit")) lecroy->ForceHDMode(true); //else auto resolution depending on instrument type @@ -2862,7 +2862,7 @@ void Session::ApplyPreferences(shared_ptr scope) auto siglent = dynamic_pointer_cast(scope); if(siglent) { - auto dataWidth = m_preferences.GetEnumRaw("Drivers.Siglent SDS HD.data_width"); + auto dataWidth = GetPreferences().GetEnumRaw("Drivers.Siglent SDS HD.data_width"); if(dataWidth == WIDTH_8_BITS) { siglent->ForceHDMode(false); @@ -2875,7 +2875,7 @@ void Session::ApplyPreferences(shared_ptr scope) auto rsrtb = dynamic_pointer_cast(scope); if(rsrtb) { - auto dataWidth = m_preferences.GetEnumRaw("Drivers.RohdeSchwarz RTB.data_width"); + auto dataWidth = GetPreferences().GetEnumRaw("Drivers.RohdeSchwarz RTB.data_width"); if(dataWidth == WIDTH_8_BITS) { rsrtb->ForceHDMode(false); @@ -2888,7 +2888,7 @@ void Session::ApplyPreferences(shared_ptr scope) auto rigol = dynamic_pointer_cast(scope); if(rigol) { - auto dataWidth = m_preferences.GetEnumRaw("Drivers.Rigol DHO.data_width"); + auto dataWidth = GetPreferences().GetEnumRaw("Drivers.Rigol DHO.data_width"); if(dataWidth == WIDTH_8_BITS) { rigol->ForceHDMode(false); diff --git a/src/ngscopeclient/Session.h b/src/ngscopeclient/Session.h index 94a95dea..3cd92f1c 100644 --- a/src/ngscopeclient/Session.h +++ b/src/ngscopeclient/Session.h @@ -593,15 +593,12 @@ class Session protected: std::optional m_hoverTime; +public: //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // End user preferences (persistent across sessions) - //Preferences state - PreferenceManager m_preferences; - -public: PreferenceManager& GetPreferences() - { return m_preferences; } + { return PreferenceManager::GetPreferences(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Reference filters (used to query legal inputs to filters etc) diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index 32fe9714..68bb8f14 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -37,6 +37,7 @@ #include "TextureManager.h" #include "VulkanWindow.h" #include "VulkanFFTPlan.h" +#include "PreferenceManager.h" using namespace std; @@ -57,7 +58,7 @@ void (*ImGui_ImplVulkan_SetWindowSize)(ImGuiViewport* viewport, ImVec2 size); /** @brief Creates a new top level window with the specified title */ -VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, bool noMaximize) +VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, bool noMaximize, bool noRestore) : m_renderQueue(queue) , m_resizeEventPending(false) , m_softwareResizeRequested(false) @@ -69,6 +70,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b , m_width(0) , m_height(0) , m_fullscreen(false) + , m_noRestore(noRestore) , m_windowedX(0) , m_windowedY(0) , m_windowedWidth(0) @@ -104,34 +106,85 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b //Scale the initial window size by the monitor DPI glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); - //Create the window - int wx, wy, ww, wh; + //Prepare window creation + int workAreaXPosition, workAreaYPosition, workAreaWidth, workAreaHeigth; + int windowXPosition, windowYPosition, windowWidth = 0, windowHeigth = 0; + bool restoreWindowPosition = false; + bool fullscreen = false; + bool maximized = false; if(noMaximize) { + //Window creation m_window = glfwCreateWindow(1280, 720, title.c_str(), nullptr, nullptr); } else { GLFWmonitor* monitor = glfwGetPrimaryMonitor(); - glfwGetMonitorWorkarea(monitor, &wx, &wy, &ww, &wh); - m_window = glfwCreateWindow(ww, wh, title.c_str(), nullptr, nullptr); + glfwGetMonitorWorkarea(monitor, &workAreaXPosition, &workAreaYPosition, &workAreaWidth, &workAreaHeigth); + LogTrace("Workarea position and size: %d %d %d %d\n", workAreaXPosition, workAreaYPosition, workAreaWidth, workAreaHeigth); + windowWidth = workAreaWidth; + windowHeigth = workAreaHeigth; + if(!noRestore) + { // Restore window size and position from preferences + PreferenceManager& preferences = PreferenceManager::GetPreferences(); + int windowWidthPref = preferences.GetInt("Appearance.Startup.startup_size_width"); + int windowHeigthPref = preferences.GetInt("Appearance.Startup.startup_size_heigth"); + int windowXPositionPref = preferences.GetInt("Appearance.Startup.startup_pos_x"); + int windowYPositionPref = preferences.GetInt("Appearance.Startup.startup_pos_y"); + fullscreen = preferences.GetBool("Appearance.Startup.startup_fullscreen"); + maximized = preferences.GetBool("Appearance.Startup.startup_maximized"); + m_width = windowWidthPref; + m_height = windowHeigthPref; + m_windowedX = windowXPositionPref; + m_windowedY = windowYPositionPref; + if(!fullscreen && !maximized) + { + if(windowWidthPref != 0 && windowHeigthPref != 0) + { // Not default values: use them + windowWidth = windowWidthPref; + windowHeigth = windowHeigthPref; + windowXPosition = windowXPositionPref; + windowYPosition = windowYPositionPref; + LogTrace("Preferences startup position and size: %d %d %d %d\n", windowXPosition, windowYPosition, windowWidthPref, windowHeigthPref); + restoreWindowPosition = true; + } + else + { // Default to maximized + maximized = true; + } + } + } + //Window creation + if(maximized) glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); + LogTrace("Creating window with size: %d %d and maximized = %d\n", windowWidth, windowHeigth, maximized); + m_window = glfwCreateWindow(windowWidth, windowHeigth, title.c_str(), nullptr, nullptr); } if(!m_window) { LogError("Window creation failed\n"); abort(); } - if(!noMaximize) + if(fullscreen) { + SetFullscreen(true); + } + else if(!noMaximize) + { // Set window size and position int left, top, right, bottom; - glfwGetWindowFrameSize(m_window, &left, &top, &right, &bottom); - glfwSetWindowMonitor(m_window, nullptr, 0, top, ww, wh-top, GLFW_DONT_CARE); - LogTrace("Window frame size: %d %d %d %d\n", top, left, right, bottom); - LogTrace("Workarea origin: %d %d\n", wx, wy); - float xscale, yscale; - glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale); - LogTrace("DPI scale: %f %f\n", xscale, yscale); + if(!restoreWindowPosition) + { // Get frame size to adjust window maximization + glfwGetWindowFrameSize(m_window, &left, &top, &right, &bottom); + LogTrace("Window frame size: %d %d %d %d\n", top, left, right, bottom); + windowXPosition = 0; + windowYPosition = top; + windowHeigth -= top; + } + LogTrace("Resizing window with postion and size: %d %d %d %d\n", windowXPosition, windowYPosition, windowWidth, windowHeigth); + glfwSetWindowMonitor(m_window, nullptr, windowXPosition, windowYPosition, windowWidth, windowHeigth, GLFW_DONT_CARE); } + float xscale, yscale; + glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale); + LogTrace("DPI scale: %f %f\n", xscale, yscale); //Create a Vulkan surface for drawing onto VkSurfaceKHR surface; @@ -656,6 +709,7 @@ void VulkanWindow::DoRender(vk::raii::CommandBuffer& /*cmdBuf*/) void VulkanWindow::SetFullscreen(bool fullscreen) { m_fullscreen = fullscreen; + if(!m_noRestore) PreferenceManager::GetPreferences().GetPreference("Appearance.Startup.startup_fullscreen").SetBool(fullscreen); if(m_fullscreen) { @@ -706,6 +760,21 @@ void VulkanWindow::SetFullscreen(bool fullscreen) } } +void VulkanWindow::SaveWindowPositionAndSize() +{ + int x, y; + glfwGetWindowPos(m_window, &x, &y); + PreferenceManager& preferences = PreferenceManager::GetPreferences(); + preferences.GetPreference("Appearance.Startup.startup_size_width").SetInt(m_width); + preferences.GetPreference("Appearance.Startup.startup_size_heigth").SetInt(m_height); + preferences.GetPreference("Appearance.Startup.startup_pos_x").SetInt(x); + preferences.GetPreference("Appearance.Startup.startup_pos_y").SetInt(y); + preferences.GetPreference("Appearance.Startup.monitor_width").SetInt(x); + preferences.GetPreference("Appearance.Startup.monitor_heigth").SetInt(y); + bool maximized = (glfwGetWindowAttrib(m_window, GLFW_MAXIMIZED) == GLFW_TRUE); + preferences.GetPreference("Appearance.Startup.startup_maximized").SetBool(maximized); +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ImGui hooks diff --git a/src/ngscopeclient/VulkanWindow.h b/src/ngscopeclient/VulkanWindow.h index 92d35b92..023a2817 100644 --- a/src/ngscopeclient/VulkanWindow.h +++ b/src/ngscopeclient/VulkanWindow.h @@ -43,7 +43,7 @@ class Texture; class VulkanWindow { public: - VulkanWindow(const std::string& title, std::shared_ptr queue, bool noMaximize); + VulkanWindow(const std::string& title, std::shared_ptr queue, bool noMaximize, bool noRestore); virtual ~VulkanWindow(); GLFWwindow* GetWindow() @@ -64,6 +64,8 @@ class VulkanWindow bool IsFullscreen() { return m_fullscreen; } + void SaveWindowPositionAndSize(); + protected: bool UpdateFramebuffer(); void SetFullscreen(bool fullscreen); @@ -158,6 +160,9 @@ class VulkanWindow ///@brief Fullscreen flag bool m_fullscreen; + ///@brief True user asked (via command line argument) not to restore previous window state + bool m_noRestore; + ///@brief Saved position before we went fullscreen int m_windowedX; diff --git a/src/ngscopeclient/main.cpp b/src/ngscopeclient/main.cpp index c385e55a..5b65af63 100644 --- a/src/ngscopeclient/main.cpp +++ b/src/ngscopeclient/main.cpp @@ -122,6 +122,7 @@ int main(int argc, char* argv[]) string sessionToOpen; bool noMaximize = false; + bool noRestore = false; vector instrumentConnectionStrings; for(int i=1; i queue(g_vkQueueManager->GetRenderQueue("g_mainWindow.render")); - g_mainWindow = make_unique(queue,noMaximize); + g_mainWindow = make_unique(queue,noMaximize,noRestore); + auto& session = g_mainWindow->GetSession(); @@ -309,6 +317,10 @@ int main(int argc, char* argv[]) //Draw the main window g_mainWindow->Render(); } + if(!noRestore && !noMaximize) + { // Store window position and size for next startup + g_mainWindow->SaveWindowPositionAndSize(); + } session.ClearBackgroundThreads(); } From ab3cd673e5f6b70216b286b32eb57041739086d3 Mon Sep 17 00:00:00 2001 From: fredzo Date: Wed, 18 Feb 2026 01:03:11 +0100 Subject: [PATCH 05/12] Fixed value init. --- src/ngscopeclient/VulkanWindow.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index 68bb8f14..c6226920 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -133,11 +133,14 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b int windowYPositionPref = preferences.GetInt("Appearance.Startup.startup_pos_y"); fullscreen = preferences.GetBool("Appearance.Startup.startup_fullscreen"); maximized = preferences.GetBool("Appearance.Startup.startup_maximized"); - m_width = windowWidthPref; - m_height = windowHeigthPref; - m_windowedX = windowXPositionPref; - m_windowedY = windowYPositionPref; - if(!fullscreen && !maximized) + if(fullscreen || maximized) + { // Save prefs for later + m_width = windowWidthPref; + m_height = windowHeigthPref; + m_windowedX = windowXPositionPref; + m_windowedY = windowYPositionPref; + } + else { if(windowWidthPref != 0 && windowHeigthPref != 0) { // Not default values: use them From 6c25efca04b3f70416a4d3efda2b1cf292a24b02 Mon Sep 17 00:00:00 2001 From: fredzo Date: Wed, 18 Feb 2026 14:14:43 +0100 Subject: [PATCH 06/12] Added monitor change detection to prevent restoring out of place window. Fixed compilation warning. --- src/ngscopeclient/PreferenceSchema.cpp | 1 + src/ngscopeclient/VulkanWindow.cpp | 64 ++++++++++++++++++++++++-- src/ngscopeclient/VulkanWindow.h | 2 + 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index fc91e45d..c9c06f5e 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -492,6 +492,7 @@ void PreferenceManager::InitializeDefaults() windowStartup.AddPreference(Preference::Int ("startup_pos_y", 0).Invisible()); windowStartup.AddPreference(Preference::Int ("startup_size_width", 0).Invisible()); windowStartup.AddPreference(Preference::Int ("startup_size_heigth", 0).Invisible()); + windowStartup.AddPreference(Preference::String ("monitor_name", "").Invisible()); windowStartup.AddPreference(Preference::Int ("monitor_width", 0).Invisible()); windowStartup.AddPreference(Preference::Int ("monitor_heigth", 0).Invisible()); diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index c6226920..450e02b9 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -108,7 +108,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b //Prepare window creation int workAreaXPosition, workAreaYPosition, workAreaWidth, workAreaHeigth; - int windowXPosition, windowYPosition, windowWidth = 0, windowHeigth = 0; + int windowXPosition = 0, windowYPosition = 0, windowWidth = 0, windowHeigth = 0; bool restoreWindowPosition = false; bool fullscreen = false; bool maximized = false; @@ -131,9 +131,12 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b int windowHeigthPref = preferences.GetInt("Appearance.Startup.startup_size_heigth"); int windowXPositionPref = preferences.GetInt("Appearance.Startup.startup_pos_x"); int windowYPositionPref = preferences.GetInt("Appearance.Startup.startup_pos_y"); + string monitorName = preferences.GetString("Appearance.Startup.monitor_name"); + int monitorWidth = preferences.GetInt("Appearance.Startup.monitor_width"); + int monitorHeigth = preferences.GetInt("Appearance.Startup.monitor_heigth"); fullscreen = preferences.GetBool("Appearance.Startup.startup_fullscreen"); maximized = preferences.GetBool("Appearance.Startup.startup_maximized"); - if(fullscreen || maximized) + if(fullscreen || maximized || !IsPositionValid(monitorName, monitorWidth, monitorHeigth, windowXPositionPref, windowYPositionPref)) { // Save prefs for later m_width = windowWidthPref; m_height = windowHeigthPref; @@ -709,6 +712,50 @@ void VulkanWindow::DoRender(vk::raii::CommandBuffer& /*cmdBuf*/) //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Window management +GLFWmonitor* VulkanWindow::GetCurrentMonitor() +{ + int nmonitors; + GLFWmonitor** monitors = glfwGetMonitors(&nmonitors); + int wx, wy; + glfwGetWindowPos(m_window, &wx, &wy); + for (int i = 0; i < nmonitors; ++i) + { + int mx, my, mw, mh; + glfwGetMonitorWorkarea(monitors[i], &mx, &my, &mw, &mh); + if (wx >= mx && wx < mx + mw && wy >= my && wy < my + mh) + { // Window's top left corner is in this monitor's working area + return monitors[i]; + } + } + // Not found + return nullptr; +} + +#define MINIMUM_WINDOW_VISIBLE_AREA_SIZE 50 + +bool VulkanWindow::IsPositionValid(const std::string monitorName, int /*monitorWidth*/, int /*monitorHeigth*/, int windowXPos, int windowYPos) +{ + int nmonitors; + GLFWmonitor** monitors = glfwGetMonitors(&nmonitors); + for (int i = 0; i < nmonitors; ++i) + { + string name = string(glfwGetMonitorName(monitors[i])); + if(name == monitorName) + { // Monitor name match, check position + int mx, my, mw, mh; + glfwGetMonitorWorkarea(monitors[i], &mx, &my, &mw, &mh); + if (windowXPos >= mx && windowXPos + MINIMUM_WINDOW_VISIBLE_AREA_SIZE < mx + mw + && windowYPos >= my && windowYPos + MINIMUM_WINDOW_VISIBLE_AREA_SIZE < my + mh) + { + return true; + } + } + } + // Not found + return false; +} + + void VulkanWindow::SetFullscreen(bool fullscreen) { m_fullscreen = fullscreen; @@ -772,10 +819,19 @@ void VulkanWindow::SaveWindowPositionAndSize() preferences.GetPreference("Appearance.Startup.startup_size_heigth").SetInt(m_height); preferences.GetPreference("Appearance.Startup.startup_pos_x").SetInt(x); preferences.GetPreference("Appearance.Startup.startup_pos_y").SetInt(y); - preferences.GetPreference("Appearance.Startup.monitor_width").SetInt(x); - preferences.GetPreference("Appearance.Startup.monitor_heigth").SetInt(y); bool maximized = (glfwGetWindowAttrib(m_window, GLFW_MAXIMIZED) == GLFW_TRUE); preferences.GetPreference("Appearance.Startup.startup_maximized").SetBool(maximized); + int monitorWidth = 0, monitorHeigth = 0; + string monitorName = ""; + GLFWmonitor* currentMonitor = GetCurrentMonitor(); + if(currentMonitor) + { + monitorName = glfwGetMonitorName(currentMonitor); + glfwGetMonitorWorkarea(currentMonitor,&x,&y,&monitorWidth,&monitorHeigth); + } + preferences.GetPreference("Appearance.Startup.monitor_width").SetInt(monitorWidth); + preferences.GetPreference("Appearance.Startup.monitor_heigth").SetInt(monitorHeigth); + preferences.GetPreference("Appearance.Startup.monitor_name").SetString(monitorName); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/ngscopeclient/VulkanWindow.h b/src/ngscopeclient/VulkanWindow.h index 023a2817..0696023a 100644 --- a/src/ngscopeclient/VulkanWindow.h +++ b/src/ngscopeclient/VulkanWindow.h @@ -69,6 +69,8 @@ class VulkanWindow protected: bool UpdateFramebuffer(); void SetFullscreen(bool fullscreen); + GLFWmonitor* GetCurrentMonitor(); + bool IsPositionValid(const std::string monitorName, int monitorWidth, int monitorHeigth, int windowXPos, int windowYPos); virtual void DoRender(vk::raii::CommandBuffer& cmdBuf); virtual void RenderUI(); From ebc15539b0909b1cad190ba67aaacb447b19fcc7 Mon Sep 17 00:00:00 2001 From: fredzo Date: Wed, 18 Feb 2026 15:52:07 +0100 Subject: [PATCH 07/12] Fixed default value handling. Fixed minimum area size. --- src/ngscopeclient/VulkanWindow.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index 450e02b9..ca97e52f 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -136,7 +136,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b int monitorHeigth = preferences.GetInt("Appearance.Startup.monitor_heigth"); fullscreen = preferences.GetBool("Appearance.Startup.startup_fullscreen"); maximized = preferences.GetBool("Appearance.Startup.startup_maximized"); - if(fullscreen || maximized || !IsPositionValid(monitorName, monitorWidth, monitorHeigth, windowXPositionPref, windowYPositionPref)) + if(fullscreen || maximized) { // Save prefs for later m_width = windowWidthPref; m_height = windowHeigthPref; @@ -145,7 +145,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b } else { - if(windowWidthPref != 0 && windowHeigthPref != 0) + if(windowWidthPref != 0 && windowHeigthPref != 0 && IsPositionValid(monitorName, monitorWidth, monitorHeigth, windowXPositionPref, windowYPositionPref)) { // Not default values: use them windowWidth = windowWidthPref; windowHeigth = windowHeigthPref; @@ -187,6 +187,8 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b } LogTrace("Resizing window with postion and size: %d %d %d %d\n", windowXPosition, windowYPosition, windowWidth, windowHeigth); glfwSetWindowMonitor(m_window, nullptr, windowXPosition, windowYPosition, windowWidth, windowHeigth, GLFW_DONT_CARE); + m_width = windowWidth; + m_height = windowHeigth; } float xscale, yscale; glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale); @@ -731,7 +733,7 @@ GLFWmonitor* VulkanWindow::GetCurrentMonitor() return nullptr; } -#define MINIMUM_WINDOW_VISIBLE_AREA_SIZE 50 +#define MINIMUM_WINDOW_VISIBLE_AREA_SIZE 100 bool VulkanWindow::IsPositionValid(const std::string monitorName, int /*monitorWidth*/, int /*monitorHeigth*/, int windowXPos, int windowYPos) { @@ -759,7 +761,6 @@ bool VulkanWindow::IsPositionValid(const std::string monitorName, int /*monitorW void VulkanWindow::SetFullscreen(bool fullscreen) { m_fullscreen = fullscreen; - if(!m_noRestore) PreferenceManager::GetPreferences().GetPreference("Appearance.Startup.startup_fullscreen").SetBool(fullscreen); if(m_fullscreen) { @@ -819,6 +820,7 @@ void VulkanWindow::SaveWindowPositionAndSize() preferences.GetPreference("Appearance.Startup.startup_size_heigth").SetInt(m_height); preferences.GetPreference("Appearance.Startup.startup_pos_x").SetInt(x); preferences.GetPreference("Appearance.Startup.startup_pos_y").SetInt(y); + preferences.GetPreference("Appearance.Startup.startup_fullscreen").SetBool(m_fullscreen); bool maximized = (glfwGetWindowAttrib(m_window, GLFW_MAXIMIZED) == GLFW_TRUE); preferences.GetPreference("Appearance.Startup.startup_maximized").SetBool(maximized); int monitorWidth = 0, monitorHeigth = 0; From b6b04aaa9cc977a15fd753473e894ac7e88bac3c Mon Sep 17 00:00:00 2001 From: fredzo Date: Wed, 18 Feb 2026 16:48:29 +0100 Subject: [PATCH 08/12] Fixed multi-monitor repositionning logic. --- src/ngscopeclient/VulkanWindow.cpp | 71 ++++++++++++++++-------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index ca97e52f..a19af736 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -136,32 +136,28 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b int monitorHeigth = preferences.GetInt("Appearance.Startup.monitor_heigth"); fullscreen = preferences.GetBool("Appearance.Startup.startup_fullscreen"); maximized = preferences.GetBool("Appearance.Startup.startup_maximized"); - if(fullscreen || maximized) - { // Save prefs for later + if(windowWidthPref != 0 && windowHeigthPref != 0 && IsPositionValid(monitorName, monitorWidth, monitorHeigth, windowXPositionPref, windowYPositionPref)) + { // Not default values: use them + windowWidth = windowWidthPref; + windowHeigth = windowHeigthPref; + windowXPosition = windowXPositionPref; + windowYPosition = windowYPositionPref; + LogTrace("Preferences startup position and size: %d %d %d %d\n", windowXPosition, windowYPosition, windowWidthPref, windowHeigthPref); + restoreWindowPosition = true; + } + else + { // Default to maximized + maximized = true; + } + if(fullscreen) + { // Save prefs for when we get out of fullscreen mode m_width = windowWidthPref; m_height = windowHeigthPref; m_windowedX = windowXPositionPref; m_windowedY = windowYPositionPref; } - else - { - if(windowWidthPref != 0 && windowHeigthPref != 0 && IsPositionValid(monitorName, monitorWidth, monitorHeigth, windowXPositionPref, windowYPositionPref)) - { // Not default values: use them - windowWidth = windowWidthPref; - windowHeigth = windowHeigthPref; - windowXPosition = windowXPositionPref; - windowYPosition = windowYPositionPref; - LogTrace("Preferences startup position and size: %d %d %d %d\n", windowXPosition, windowYPosition, windowWidthPref, windowHeigthPref); - restoreWindowPosition = true; - } - else - { // Default to maximized - maximized = true; - } - } } //Window creation - if(maximized) glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); LogTrace("Creating window with size: %d %d and maximized = %d\n", windowWidth, windowHeigth, maximized); m_window = glfwCreateWindow(windowWidth, windowHeigth, title.c_str(), nullptr, nullptr); } @@ -170,11 +166,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b LogError("Window creation failed\n"); abort(); } - if(fullscreen) - { - SetFullscreen(true); - } - else if(!noMaximize) + if(!noMaximize) { // Set window size and position int left, top, right, bottom; if(!restoreWindowPosition) @@ -187,9 +179,14 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b } LogTrace("Resizing window with postion and size: %d %d %d %d\n", windowXPosition, windowYPosition, windowWidth, windowHeigth); glfwSetWindowMonitor(m_window, nullptr, windowXPosition, windowYPosition, windowWidth, windowHeigth, GLFW_DONT_CARE); + if(maximized) glfwMaximizeWindow(m_window); m_width = windowWidth; m_height = windowHeigth; } + if(fullscreen) + { + SetFullscreen(true); + } float xscale, yscale; glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale); LogTrace("DPI scale: %f %f\n", xscale, yscale); @@ -735,25 +732,31 @@ GLFWmonitor* VulkanWindow::GetCurrentMonitor() #define MINIMUM_WINDOW_VISIBLE_AREA_SIZE 100 -bool VulkanWindow::IsPositionValid(const std::string monitorName, int /*monitorWidth*/, int /*monitorHeigth*/, int windowXPos, int windowYPos) +bool VulkanWindow::IsPositionValid(const std::string monitorName, int monitorWidth, int monitorHeigth, int windowXPos, int windowYPos) { int nmonitors; GLFWmonitor** monitors = glfwGetMonitors(&nmonitors); for (int i = 0; i < nmonitors; ++i) { - string name = string(glfwGetMonitorName(monitors[i])); - if(name == monitorName) - { // Monitor name match, check position - int mx, my, mw, mh; - glfwGetMonitorWorkarea(monitors[i], &mx, &my, &mw, &mh); - if (windowXPos >= mx && windowXPos + MINIMUM_WINDOW_VISIBLE_AREA_SIZE < mx + mw - && windowYPos >= my && windowYPos + MINIMUM_WINDOW_VISIBLE_AREA_SIZE < my + mh) - { + int mx, my, mw, mh; + glfwGetMonitorWorkarea(monitors[i], &mx, &my, &mw, &mh); + LogTrace("Checking monitor with position and size: %d %d %d %d for window pos %d %d and orginal monotir size %d %d\n", mx, my, mx, mh, windowXPos, windowYPos, monitorWidth, monitorHeigth); + if (windowXPos >= mx && windowXPos + MINIMUM_WINDOW_VISIBLE_AREA_SIZE < mx + mw + && windowYPos >= my && windowYPos + MINIMUM_WINDOW_VISIBLE_AREA_SIZE < my + mh) + { // Check position name since several monitors can share the same name + string name = string(glfwGetMonitorName(monitors[i])); + LogTrace("Found match for name %s (original %s)\n", name.c_str(), monitorName.c_str()); + if(name == monitorName && mw == monitorWidth && mh == monitorHeigth) + { // Monitor name and size match return true; } + else + { // Monitor configuration has changed + return false; + } } } - // Not found + // Not found return false; } From 564aae2762825ba3364bce2a937e955c1dc62b54 Mon Sep 17 00:00:00 2001 From: fredzo Date: Wed, 18 Feb 2026 19:20:58 +0100 Subject: [PATCH 09/12] Added DPI scaling override with environment variables. Fixes https://github.com/ngscopeclient/scopehal-apps/issues/953 --- src/ngscopeclient/MainWindow.cpp | 2 +- src/ngscopeclient/VulkanWindow.cpp | 27 ++++++++++++++++++++++----- src/ngscopeclient/VulkanWindow.h | 11 ++++++++++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index a911edfb..4a330286 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -610,7 +610,7 @@ void MainWindow::ResetStyle() style.FontSizeBase = oldStyle.FontSizeBase; style.FontScaleMain = oldStyle.FontScaleMain; style.FontScaleDpi = oldStyle.FontScaleDpi; - style.ScaleAllSizes(style.FontScaleDpi); + style.ScaleAllSizes(VulkanWindow::m_forceDPIScaling ? VulkanWindow::m_forcedUIScale : style.FontScaleDpi); switch(m_session.GetPreferences().GetEnumRaw("Appearance.General.theme")) { diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index a19af736..0439efce 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -85,8 +85,27 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; - io.ConfigDpiScaleFonts = true; - io.ConfigDpiScaleViewports = true; + // Check for environment variable DPI scaling override + const char *font_scale_override = getenv("NGSCOPECLIENT_FONT_SCALE"); + const char *ui_scale_override = getenv("NGSCOPECLIENT_UI_SCALE"); + if(font_scale_override || ui_scale_override) + { + m_forceDPIScaling = true; + if(font_scale_override) + { + m_forcedFontScale = atof(font_scale_override); + } + if(ui_scale_override) + { + m_forcedUIScale = atof(ui_scale_override); + } + LogTrace("Forcing DPI scaling with font scale %f and UI scale %f\n",m_forcedFontScale,m_forcedUIScale); + } + else + { + io.ConfigDpiScaleFonts = true; + io.ConfigDpiScaleViewports = true; + } //Don't serialize UI config for now //TODO: serialize to scopesession or something? https://github.com/ocornut/imgui/issues/4294 @@ -187,9 +206,6 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b { SetFullscreen(true); } - float xscale, yscale; - glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale); - LogTrace("DPI scale: %f %f\n", xscale, yscale); //Create a Vulkan surface for drawing onto VkSurfaceKHR surface; @@ -258,6 +274,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b info.ImageCount = m_backBuffers.size(); info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; info.PipelineInfoMain.RenderPass = **m_renderPass; + if(m_forceDPIScaling) ImGui::GetStyle().FontScaleMain = m_forcedFontScale; //HERE BE DRAGONS: // We're handing imgui a VkQueue here without holding the lock. diff --git a/src/ngscopeclient/VulkanWindow.h b/src/ngscopeclient/VulkanWindow.h index 0696023a..7973ee79 100644 --- a/src/ngscopeclient/VulkanWindow.h +++ b/src/ngscopeclient/VulkanWindow.h @@ -162,9 +162,18 @@ class VulkanWindow ///@brief Fullscreen flag bool m_fullscreen; - ///@brief True user asked (via command line argument) not to restore previous window state + ///@brief True if user asked (via command line argument) not to restore previous window state bool m_noRestore; + ///@brief True if user asked (via NGSCOPECLIENT_UI_SCALE or NGSCOPECLIENT_FONT_SCALE environment variable) to force DPI scaling + bool m_forceDPIScaling; + + ///@brief Forced font DPI scale value + float m_forcedFontScale = 1.0f; + + ///@brief Forced UI DPI scale value + float m_forcedUIScale = 1.0f; + ///@brief Saved position before we went fullscreen int m_windowedX; From a1b346ca8b605d1ad6e19638fd68f31e35678380 Mon Sep 17 00:00:00 2001 From: fredzo Date: Wed, 18 Feb 2026 19:50:18 +0100 Subject: [PATCH 10/12] Fixed comments. --- src/ngscopeclient/VulkanWindow.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index 0439efce..100bc6df 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -132,12 +132,11 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b bool fullscreen = false; bool maximized = false; if(noMaximize) - { - //Window creation + { // Window creation with fixed size m_window = glfwCreateWindow(1280, 720, title.c_str(), nullptr, nullptr); } else - { + { // Get primary monitor's content area for default sizing GLFWmonitor* monitor = glfwGetPrimaryMonitor(); glfwGetMonitorWorkarea(monitor, &workAreaXPosition, &workAreaYPosition, &workAreaWidth, &workAreaHeigth); LogTrace("Workarea position and size: %d %d %d %d\n", workAreaXPosition, workAreaYPosition, workAreaWidth, workAreaHeigth); @@ -156,7 +155,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b fullscreen = preferences.GetBool("Appearance.Startup.startup_fullscreen"); maximized = preferences.GetBool("Appearance.Startup.startup_maximized"); if(windowWidthPref != 0 && windowHeigthPref != 0 && IsPositionValid(monitorName, monitorWidth, monitorHeigth, windowXPositionPref, windowYPositionPref)) - { // Not default values: use them + { // We have stored position and size: use them windowWidth = windowWidthPref; windowHeigth = windowHeigthPref; windowXPosition = windowXPositionPref; @@ -165,7 +164,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b restoreWindowPosition = true; } else - { // Default to maximized + { // No previous position: default to maximized maximized = true; } if(fullscreen) @@ -176,7 +175,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b m_windowedY = windowYPositionPref; } } - //Window creation + // Create the window with calculated size on default monitor, we will reposition it after if needed LogTrace("Creating window with size: %d %d and maximized = %d\n", windowWidth, windowHeigth, maximized); m_window = glfwCreateWindow(windowWidth, windowHeigth, title.c_str(), nullptr, nullptr); } @@ -186,10 +185,10 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b abort(); } if(!noMaximize) - { // Set window size and position + { // Now that the window has been created, we can set its position and size correctly int left, top, right, bottom; if(!restoreWindowPosition) - { // Get frame size to adjust window maximization + { // Get frame size to adjust window default position and size accordingly glfwGetWindowFrameSize(m_window, &left, &top, &right, &bottom); LogTrace("Window frame size: %d %d %d %d\n", top, left, right, bottom); windowXPosition = 0; @@ -197,6 +196,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b windowHeigth -= top; } LogTrace("Resizing window with postion and size: %d %d %d %d\n", windowXPosition, windowYPosition, windowWidth, windowHeigth); + // Actually set the window position and size with calculated values glfwSetWindowMonitor(m_window, nullptr, windowXPosition, windowYPosition, windowWidth, windowHeigth, GLFW_DONT_CARE); if(maximized) glfwMaximizeWindow(m_window); m_width = windowWidth; @@ -730,11 +730,11 @@ void VulkanWindow::DoRender(vk::raii::CommandBuffer& /*cmdBuf*/) GLFWmonitor* VulkanWindow::GetCurrentMonitor() { - int nmonitors; - GLFWmonitor** monitors = glfwGetMonitors(&nmonitors); + int monitorNumber; + GLFWmonitor** monitors = glfwGetMonitors(&monitorNumber); int wx, wy; glfwGetWindowPos(m_window, &wx, &wy); - for (int i = 0; i < nmonitors; ++i) + for (int i = 0; i < monitorNumber; ++i) { int mx, my, mw, mh; glfwGetMonitorWorkarea(monitors[i], &mx, &my, &mw, &mh); @@ -751,9 +751,9 @@ GLFWmonitor* VulkanWindow::GetCurrentMonitor() bool VulkanWindow::IsPositionValid(const std::string monitorName, int monitorWidth, int monitorHeigth, int windowXPos, int windowYPos) { - int nmonitors; - GLFWmonitor** monitors = glfwGetMonitors(&nmonitors); - for (int i = 0; i < nmonitors; ++i) + int monitorNumber; + GLFWmonitor** monitors = glfwGetMonitors(&monitorNumber); + for (int i = 0; i < monitorNumber; ++i) { int mx, my, mw, mh; glfwGetMonitorWorkarea(monitors[i], &mx, &my, &mw, &mh); From 47dc4514ce63b45d993b9c38f5106027398a0acf Mon Sep 17 00:00:00 2001 From: fredzo Date: Wed, 18 Feb 2026 23:16:34 +0100 Subject: [PATCH 11/12] Added preference for window startup mode. Made widowed the default mode. Changed command line arguments to --restore and --maximized --- src/ngscopeclient/MainWindow.cpp | 4 +-- src/ngscopeclient/MainWindow.h | 2 +- src/ngscopeclient/PreferenceSchema.cpp | 15 +++++++++ src/ngscopeclient/PreferenceTypes.h | 7 ++++ src/ngscopeclient/VulkanWindow.cpp | 45 ++++++++++++++++++++++---- src/ngscopeclient/VulkanWindow.h | 2 +- src/ngscopeclient/main.cpp | 20 ++++++------ 7 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/ngscopeclient/MainWindow.cpp b/src/ngscopeclient/MainWindow.cpp index 4a330286..01df36ce 100644 --- a/src/ngscopeclient/MainWindow.cpp +++ b/src/ngscopeclient/MainWindow.cpp @@ -115,8 +115,8 @@ static void MainWindow_OnChangedViewport([[maybe_unused]] ImGuiViewport *vp) #define DBG_SUFFIX "" #endif -MainWindow::MainWindow(shared_ptr queue, bool noMaximize, bool noRestore) - : VulkanWindow("ngscopeclient " NGSCOPECLIENT_VERSION " " DBG_SUFFIX SAN_SUFFIX, queue, noMaximize, noRestore) +MainWindow::MainWindow(shared_ptr queue, bool maximized, bool restored) + : VulkanWindow("ngscopeclient " NGSCOPECLIENT_VERSION " " DBG_SUFFIX SAN_SUFFIX, queue, maximized, restored) , m_showDemo(false) , m_nextWaveformGroup(1) , m_toolbarIconSize(0) diff --git a/src/ngscopeclient/MainWindow.h b/src/ngscopeclient/MainWindow.h index 6ed57d17..abad8054 100644 --- a/src/ngscopeclient/MainWindow.h +++ b/src/ngscopeclient/MainWindow.h @@ -164,7 +164,7 @@ class DockDialogRequest class MainWindow : public VulkanWindow { public: - MainWindow(std::shared_ptr queue, bool noMaximize, bool noRestore); + MainWindow(std::shared_ptr queue, bool maximized, bool restored); virtual ~MainWindow(); static bool OnMemoryPressureStatic(MemoryPressureLevel level, MemoryPressureType type, size_t requestedSize); diff --git a/src/ngscopeclient/PreferenceSchema.cpp b/src/ngscopeclient/PreferenceSchema.cpp index c9c06f5e..8e9699bd 100644 --- a/src/ngscopeclient/PreferenceSchema.cpp +++ b/src/ngscopeclient/PreferenceSchema.cpp @@ -485,6 +485,21 @@ void PreferenceManager::InitializeDefaults() .EnumValue("Multi window", VIEWPORT_ENABLE) .EnumValue("Single window", VIEWPORT_DISABLE) ); + windows.AddPreference( + Preference::Enum("startup_mode", STARTUP_MODE_WINDOWED) + .Label("Window startup mode") + .Description( + "Specifies the way Ngscopeclient window should be opened at startup.\n" + "\n" + "The default is windowed: the application is started in a fixed 1280x720 window.\n" + "Other options are:\n" + " - Maximized: the window is maximized on the main screen,\n" + " - Last State: the window is restored at the last position and size.\n" + ) + .EnumValue("Windowed", STARTUP_MODE_WINDOWED) + .EnumValue("Maximized", STARTUP_MODE_MAXIMIZED) + .EnumValue("Last State", STARTUP_MODE_LAST_STATE) + ); auto& windowStartup = appearance.AddCategory("Startup"); windowStartup.AddPreference(Preference::Bool("startup_fullscreen", false).Invisible()); windowStartup.AddPreference(Preference::Bool("startup_maximized", false).Invisible()); diff --git a/src/ngscopeclient/PreferenceTypes.h b/src/ngscopeclient/PreferenceTypes.h index 60a6cbfd..35eb2749 100644 --- a/src/ngscopeclient/PreferenceTypes.h +++ b/src/ngscopeclient/PreferenceTypes.h @@ -55,6 +55,13 @@ enum ViewportMode VIEWPORT_DISABLE }; +enum StartupMode +{ + STARTUP_MODE_WINDOWED, + STARTUP_MODE_MAXIMIZED, + STARTUP_MODE_LAST_STATE +}; + enum DataWidth { WIDTH_AUTO, diff --git a/src/ngscopeclient/VulkanWindow.cpp b/src/ngscopeclient/VulkanWindow.cpp index 100bc6df..2b6b6ab2 100644 --- a/src/ngscopeclient/VulkanWindow.cpp +++ b/src/ngscopeclient/VulkanWindow.cpp @@ -38,6 +38,7 @@ #include "VulkanWindow.h" #include "VulkanFFTPlan.h" #include "PreferenceManager.h" +#include "PreferenceTypes.h" using namespace std; @@ -58,7 +59,7 @@ void (*ImGui_ImplVulkan_SetWindowSize)(ImGuiViewport* viewport, ImVec2 size); /** @brief Creates a new top level window with the specified title */ -VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, bool noMaximize, bool noRestore) +VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, bool maximize, bool restore) : m_renderQueue(queue) , m_resizeEventPending(false) , m_softwareResizeRequested(false) @@ -70,7 +71,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b , m_width(0) , m_height(0) , m_fullscreen(false) - , m_noRestore(noRestore) + , m_restore(restore) , m_windowedX(0) , m_windowedY(0) , m_windowedWidth(0) @@ -125,13 +126,44 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b //Scale the initial window size by the monitor DPI glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + // Determine window creation mode according to preferences and command line arguments + bool maximized = false; + bool restored = false; + bool windowed = false; + PreferenceManager& preferences = PreferenceManager::GetPreferences(); + // Priority to command line arguments + if(maximize) + { + maximized = true; + } + else if(restore) + { + restored = true; + } + else + { // No command line argument, check for preferences + auto mode = preferences.GetEnumRaw("Appearance.Windowing.startup_mode"); + switch(mode) + { + case STARTUP_MODE_MAXIMIZED: + maximized = true; + break; + case STARTUP_MODE_LAST_STATE: + restored = true; + break; + case STARTUP_MODE_WINDOWED: + default: + windowed = true; + break; + } + } + //Prepare window creation int workAreaXPosition, workAreaYPosition, workAreaWidth, workAreaHeigth; int windowXPosition = 0, windowYPosition = 0, windowWidth = 0, windowHeigth = 0; bool restoreWindowPosition = false; bool fullscreen = false; - bool maximized = false; - if(noMaximize) + if(windowed) { // Window creation with fixed size m_window = glfwCreateWindow(1280, 720, title.c_str(), nullptr, nullptr); } @@ -142,9 +174,8 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b LogTrace("Workarea position and size: %d %d %d %d\n", workAreaXPosition, workAreaYPosition, workAreaWidth, workAreaHeigth); windowWidth = workAreaWidth; windowHeigth = workAreaHeigth; - if(!noRestore) + if(restored) { // Restore window size and position from preferences - PreferenceManager& preferences = PreferenceManager::GetPreferences(); int windowWidthPref = preferences.GetInt("Appearance.Startup.startup_size_width"); int windowHeigthPref = preferences.GetInt("Appearance.Startup.startup_size_heigth"); int windowXPositionPref = preferences.GetInt("Appearance.Startup.startup_pos_x"); @@ -184,7 +215,7 @@ VulkanWindow::VulkanWindow(const string& title, shared_ptr queue, b LogError("Window creation failed\n"); abort(); } - if(!noMaximize) + if(!windowed) { // Now that the window has been created, we can set its position and size correctly int left, top, right, bottom; if(!restoreWindowPosition) diff --git a/src/ngscopeclient/VulkanWindow.h b/src/ngscopeclient/VulkanWindow.h index 7973ee79..57b5239b 100644 --- a/src/ngscopeclient/VulkanWindow.h +++ b/src/ngscopeclient/VulkanWindow.h @@ -163,7 +163,7 @@ class VulkanWindow bool m_fullscreen; ///@brief True if user asked (via command line argument) not to restore previous window state - bool m_noRestore; + bool m_restore; ///@brief True if user asked (via NGSCOPECLIENT_UI_SCALE or NGSCOPECLIENT_FONT_SCALE environment variable) to force DPI scaling bool m_forceDPIScaling; diff --git a/src/ngscopeclient/main.cpp b/src/ngscopeclient/main.cpp index 5b65af63..d29dc4c0 100644 --- a/src/ngscopeclient/main.cpp +++ b/src/ngscopeclient/main.cpp @@ -121,8 +121,8 @@ int main(int argc, char* argv[]) #endif string sessionToOpen; - bool noMaximize = false; - bool noRestore = false; + bool maximize = false; + bool restore = false; vector instrumentConnectionStrings; for(int i=1; i queue(g_vkQueueManager->GetRenderQueue("g_mainWindow.render")); - g_mainWindow = make_unique(queue,noMaximize,noRestore); + g_mainWindow = make_unique(queue,maximize,restore); auto& session = g_mainWindow->GetSession(); @@ -317,10 +317,8 @@ int main(int argc, char* argv[]) //Draw the main window g_mainWindow->Render(); } - if(!noRestore && !noMaximize) - { // Store window position and size for next startup - g_mainWindow->SaveWindowPositionAndSize(); - } + // Store window position and size for next startup + g_mainWindow->SaveWindowPositionAndSize(); session.ClearBackgroundThreads(); } From 3cca789fa32472959bc2d843baf48263433fd0de Mon Sep 17 00:00:00 2001 From: fredzo Date: Thu, 19 Feb 2026 00:15:09 +0100 Subject: [PATCH 12/12] Fixed --help doc. --- src/ngscopeclient/main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ngscopeclient/main.cpp b/src/ngscopeclient/main.cpp index d29dc4c0..09f367c9 100644 --- a/src/ngscopeclient/main.cpp +++ b/src/ngscopeclient/main.cpp @@ -57,8 +57,10 @@ static void print_help(FILE* stream) "ngscopeclient is a test and measurement remote control and analysis suite\n" "\n" "General options:\n" - " --version print the application version and exit\n" - " --help, -h print this help and exit\n" + " --version print the application version and exit\n" + " --help, -h print this help and exit\n" + " --maximize, -m maximize ngscopeclient window on startup\n" + " --restore, -r restore previous ngscopeclient window size and position\n" "\n" "Logging options:\n" " -q, --quiet make logging one level quieter (can be repeated)\n"