From 54c360484f904b7705d61e683b3824b70d37d8ff Mon Sep 17 00:00:00 2001 From: Aemony Date: Fri, 20 Mar 2026 21:07:23 +0100 Subject: [PATCH 01/23] Save to Disk + Bug fixes - Configurable auto-save behavior (defaults to on) for all capture modes - Fixed keybindings closing Settings when using Escape - Fixed keybindings not showing after a restart - Fixed keybindings resetting to the default if cleared after a restart - Added "DELETE to reset to default" option during keybinding - Fixed an ImGui Push/Pop bug or two --- include/utility/image.h | 11 +- include/utility/registry.h | 12 +- include/utility/sk_utility.h | 7 +- include/utility/skif_imgui.h | 2 +- include/utility/utility.h | 17 ++- src/SKIV.cpp | 20 +++- src/tabs/settings.cpp | 216 +++++++++++++++++++++++------------ src/tabs/viewer.cpp | 4 +- src/utility/image.cpp | 9 +- src/utility/registry.cpp | 12 +- src/utility/sk_utility.cpp | 37 +++++- src/utility/utility.cpp | 2 + 12 files changed, 238 insertions(+), 111 deletions(-) diff --git a/include/utility/image.h b/include/utility/image.h index 06f5c98..21cdf2c 100644 --- a/include/utility/image.h +++ b/include/utility/image.h @@ -4,6 +4,7 @@ #include #include #include +#include "utility.h" #pragma warning( push ) #pragma warning( disable : 4305 ) @@ -135,13 +136,15 @@ static const ParamsPQ PQ = #pragma warning( pop ) struct SKIV_Region { - ImRect _rect; - std::wstring _title; + ImRect _rect; + std::wstring _title; + CaptureMode _mode; - SKIV_Region (ImRect r_ = ImRect(), std::wstring t_ = L"") + SKIV_Region (ImRect r_, std::wstring t_, CaptureMode m_) { _rect = r_; _title = t_; + _mode = m_; } }; @@ -152,7 +155,7 @@ float SKIV_Image_LinearToPQY (float N); DirectX::XMVECTOR SKIV_Image_Rec709toICtCp (DirectX::XMVECTOR N); DirectX::XMVECTOR SKIV_Image_ICtCptoRec709 (DirectX::XMVECTOR N); -bool SKIV_Image_CopyToClipboard (const DirectX::Image* pImage, bool isHDR, bool isTemp, const wchar_t* wszFileName); +bool SKIV_Image_CopyToClipboard (const DirectX::Image* pImage, bool isHDR, CaptureMode mode, const wchar_t* wszFileName); HRESULT SKIV_Image_SaveToDisk_HDR (const DirectX::Image& image, const wchar_t* wszFileName); HRESULT SKIV_Image_SaveToDisk_SDR (const DirectX::Image& image, const wchar_t* wszFileName, bool force_sRGB); HRESULT SKIV_Image_CaptureDesktop (DirectX::ScratchImage& image, POINT pos, int flags = 0x0); diff --git a/include/utility/registry.h b/include/utility/registry.h index 00b3d7f..c5ee592 100644 --- a/include/utility/registry.h +++ b/include/utility/registry.h @@ -4,7 +4,7 @@ #include #include #include -#include "sk_utility.h" +#include "utility.h" #ifndef RRF_SUBKEY_WOW6464KEY #define RRF_SUBKEY_WOW6464KEY 0x00010000 @@ -265,10 +265,6 @@ struct SKIF_RegistrySettings { SKIF_MakeRegKeyB ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\)", LR"(99th Percentile MaxCLL)" ); - KeyValue regKVSaveScreenshots = - SKIF_MakeRegKeyB ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\)", - LR"(Save Screenshots)" ); - // Integers (DWORDs) KeyValue regKVImageScaling = @@ -347,6 +343,10 @@ struct SKIF_RegistrySettings { SKIF_MakeRegKeyI ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\PNG\)", LR"(HDR BitDepth)" ); + KeyValue regKVScreenshotsAutosave = + SKIF_MakeRegKeyI ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\)", + LR"(Screenshots Autosave)" ); + // Wide Strings KeyValue regKVIgnoreUpdate = @@ -431,6 +431,7 @@ struct SKIF_RegistrySettings { int iHDRToneMapType = 8; // 0 = Do Nothing, 1 = Clip Luminance, 8 = Map to Display int iUIMode = 1; // 0 = Safe Mode (BitBlt), 1 = Normal, 2 = VRR Compatibility int iDiagnostics = 1; // 0 = None, 1 = Normal, 2 = Enhanced (not actually used yet) + CaptureMode eScreenshotsAutosave = CaptureMode_ALL; // Default to saving all types of screen captures // Default settings (booleans) bool bAdjustWindow = false; // Adjust window size based on the image size? @@ -459,7 +460,6 @@ struct SKIF_RegistrySettings { bool bFadeCovers = true; bool bControllers = true; // Should SKIF support controller input ? bool bLoggingDeveloper = false; // This is a log level "above" verbose logging that also includes stuff like window messages. Only useable for SKIF developers - bool bSaveScreenshots = true; // Wide strings std::wstring wsUpdateChannel = L"Website"; // Default to stable channel diff --git a/include/utility/sk_utility.h b/include/utility/sk_utility.h index 517751d..91ed429 100644 --- a/include/utility/sk_utility.h +++ b/include/utility/sk_utility.h @@ -806,15 +806,16 @@ struct SK_KeybindMultiState const char* bind_name = nullptr; bool assigning = false, state = false; - SK_Keybind saved, pending; + SK_Keybind default, pending, saved; // This empty object is used during assignment to disable hotkeys temporarily static constexpr SK_Keybind disabled = { }; SK_KeybindMultiState (const char* _n, std::wstring _h) { bind_name = _n; - pending.human_readable = _h; - pending.human_readable_utf8 = SK_WideCharToUTF8 (pending.human_readable); + default.human_readable = _h; + default.human_readable_utf8 = SK_WideCharToUTF8 (default.human_readable); + pending = default; }; bool applyChanges (void); // This applies the pending changes diff --git a/include/utility/skif_imgui.h b/include/utility/skif_imgui.h index 05c3966..25512fd 100644 --- a/include/utility/skif_imgui.h +++ b/include/utility/skif_imgui.h @@ -90,4 +90,4 @@ static ImVec4 SKIF_ImGui_ImDerp (const ImVec4& a, const ImVec4& b, float t extern PopupState PopupMessageInfo; // App Mode: show an informational message box with text set through SKIF_ImGui_InfoMessage // Fonts -extern ImFont* fontConsolas; +extern ImFont* fontConsolas; \ No newline at end of file diff --git a/include/utility/utility.h b/include/utility/utility.h index 2bbea2a..d06d927 100644 --- a/include/utility/utility.h +++ b/include/utility/utility.h @@ -57,12 +57,21 @@ enum UITab { UITab_ALL // Total number of elements in enum (technically against Microsoft's enum design guidelines, but whatever) }; -enum CaptureMode { - CaptureMode_Window, - CaptureMode_Region, - CaptureMode_Screen +typedef unsigned int CaptureMode; // -> enum CaptureMode_ +enum CaptureMode_ { + CaptureMode_None = 0, + + CaptureMode_Window = 1 << 0, + CaptureMode_Region = 1 << 1, + CaptureMode_Screen = 1 << 2, + + CaptureMode_ALL = + CaptureMode_Window | CaptureMode_Region | CaptureMode_Screen }; +// Workaround for SKIF_ImGui_IsAnyPopupOpen() not detecting keybind popups +extern bool g_activeKeybindPopup; + struct FileSignature { std::wstring mime_type = L""; std::vector file_extensions = { }; diff --git a/src/SKIV.cpp b/src/SKIV.cpp index 1b28938..ea40793 100644 --- a/src/SKIV.cpp +++ b/src/SKIV.cpp @@ -1731,6 +1731,9 @@ wWinMain ( _In_ HINSTANCE hInstance, { SKIF_FrameCount.store(ImGui::GetFrameCount()); + // Reset every frame + g_activeKeybindPopup = false; + // Update hotkey variables hotkeyF1 = ( ImGui::GetKeyData (ImGuiKey_F1 )->DownDuration == 0.0f); // Switch to Viewer hotkeyF2 = ( ImGui::GetKeyData (ImGuiKey_F2 )->DownDuration == 0.0f); // Switch to Settings @@ -2167,7 +2170,7 @@ wWinMain ( _In_ HINSTANCE hInstance, ImRect allowable (SKIV_DesktopImage._desktop_pos, SKIV_DesktopImage._desktop_pos + resolution); - SKIV_Region capture_area; + SKIV_Region capture_area = SKIV_Region (ImRect(), L"", CaptureMode_None); bool HDR_Image = SKIV_DesktopImage._hdr_image; bool SKIV_HDR = (HDR_Image ? SKIF_ImGui_IsViewportHDR (SKIF_ImGui_hWnd) : false); @@ -2200,8 +2203,8 @@ wWinMain ( _In_ HINSTANCE hInstance, draw_list->AddRectFilled (allowable.Min, allowable.Max, ImGui::GetColorU32 (IM_COL32 (0, 0, 0, 20))); } - static SKIV_Region selection = SKIV_Region (ImRect(), L"Desktop_Region"); - static SKIV_Region selection_auto = SKIV_Region (ImRect(), L"Desktop_Auto"); + static SKIV_Region selection = SKIV_Region (ImRect(), L"Desktop_Region", CaptureMode_Region); + static SKIV_Region selection_auto = SKIV_Region (ImRect(), L"Desktop_Auto", CaptureMode_Region); if (GetForegroundWindow () != SKIF_ImGui_hWnd) SetForegroundWindow ( SKIF_ImGui_hWnd); @@ -2430,6 +2433,7 @@ wWinMain ( _In_ HINSTANCE hInstance, _registry._SnippingModeExit = true; _GetRectBelowCursor (&selection, false); capture_area = selection; + capture_area._mode = (_registry.eScreenshotsAutosave & CaptureMode_Region) ? CaptureMode_Region : CaptureMode_None; } else if (! ImGui::IsMouseDragging (ImGuiMouseButton_Left)) @@ -2447,6 +2451,7 @@ wWinMain ( _In_ HINSTANCE hInstance, clicked = false; _registry._SnippingModeExit = true; capture_area = selection_auto; + capture_area._mode = (_registry.eScreenshotsAutosave & CaptureMode_Region) ? CaptureMode_Region : CaptureMode_None; } else if (selection_auto._rect.Min != selection_auto._rect.Max) @@ -2753,7 +2758,8 @@ wWinMain ( _In_ HINSTANCE hInstance, bKeepProcessAlive = false; break; case UITab_Settings: - SKIF_Tab_ChangeTo = UITab_Viewer; + if (! g_activeKeybindPopup) + SKIF_Tab_ChangeTo = UITab_Viewer; break; case UITab_About: break; @@ -4184,7 +4190,8 @@ SKIF_WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) static_cast (capture_rect.top ), static_cast (capture_rect.right ), static_cast (capture_rect.bottom)), - filename + filename, + ((_registry.eScreenshotsAutosave & mode) == CaptureMode_Window) ? CaptureMode_Window : CaptureMode_None ); //PLOG_VERBOSE << "capture_rect.left : " << capture_rect.left; @@ -4203,7 +4210,8 @@ SKIF_WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) SKIV_Region ( ImRect (ImVec2 (0, 0), SKIV_DesktopImage._resolution), - filename + filename, + ((_registry.eScreenshotsAutosave & mode) == CaptureMode_Screen) ? CaptureMode_Screen : CaptureMode_None ); SKIV_Image_CaptureRegion (region); diff --git a/src/tabs/settings.cpp b/src/tabs/settings.cpp index a01e419..b89b6b6 100644 --- a/src/tabs/settings.cpp +++ b/src/tabs/settings.cpp @@ -20,12 +20,38 @@ extern bool allowShortcutCtrlA; +struct kb_kv_s +{ + SK_KeybindMultiState* _key; + SKIF_RegistrySettings::KeyValue * _reg; + std::function _callback; + std::function _show; +}; + +struct m_s +{ + CaptureMode _type; + char* _label; + char* _unique_id; + bool _value; + kb_kv_s* _keybind; +}; + void SKIF_UI_Tab_DrawSettings (void) { static SKIF_CommonPathsCache& _path_cache = SKIF_CommonPathsCache::GetInstance ( ); static SKIF_RegistrySettings& _registry = SKIF_RegistrySettings::GetInstance ( ); - + + static kb_kv_s kbToggleHDRDisplay = + { &_registry.kbToggleHDRDisplay, &_registry.regKVHotkeyToggleHDRDisplay, { [](SK_KeybindMultiState* ptr) { SKIF_Util_RegisterHotKeyHDRToggle (ptr->getKeybind() ); } }, { []() { return (SKIF_Util_IsWindows10v1709OrGreater ( ) && SKIF_Util_IsHDRSupported (NULL)); } } }; + static kb_kv_s kbCaptureWindow = + { &_registry.kbCaptureWindow, &_registry.regKVHotkeyCaptureWindow, { [](SK_KeybindMultiState* ptr) { SKIF_Util_RegisterHotKeyCapture (CaptureMode_Window, ptr->getKeybind()); } }, { []() { return true; } } }; + static kb_kv_s kbCaptureRegion = + { &_registry.kbCaptureRegion, &_registry.regKVHotkeyCaptureRegion, { [](SK_KeybindMultiState* ptr) { SKIF_Util_RegisterHotKeyCapture (CaptureMode_Region, ptr->getKeybind()); } }, { []() { return true; } } }; + static kb_kv_s kbCaptureScreen = + { &_registry.kbCaptureScreen, &_registry.regKVHotkeyCaptureScreen, { [](SK_KeybindMultiState* ptr) { SKIF_Util_RegisterHotKeyCapture (CaptureMode_Screen, ptr->getKeybind()); } }, { []() { return true; } } }; + if (ImGui::Button (ICON_FA_LEFT_LONG " Go back###GoBackBtn1", ImVec2 (150.0f * SKIF_ImGui_GlobalDPIScale, 30.0f * SKIF_ImGui_GlobalDPIScale))) SKIF_Tab_ChangeTo = UITab_Viewer; @@ -69,19 +95,15 @@ SKIF_UI_Tab_DrawSettings (void) ImGui::PushStyleColor ( ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_TextBase) ); - SKIF_ImGui_Spacing ( ); - - if ( ImGui::Checkbox ( "Save screenshots", &_registry.bSaveScreenshots ) ) - _registry.regKVSaveScreenshots.putData ( _registry.bSaveScreenshots); - - ImGui::TreePush ("ScreenshotsFolder"); - - if (! _registry.bSaveScreenshots) - SKIF_ImGui_PushDisableState ( ); - - if (ImGui::Button ("Browse")) + + ImGui::TextColored ( + ImGui::GetStyleColorVec4(ImGuiCol_SKIF_TextCaption), + "Screenshots Folder: " + ); + ImGui::SameLine ( ); + if (ImGui::Selectable(_path_cache.skiv_screenshotsA)) { std::wstring newPath = SKIF_Util_FileExplorer_BrowseFolder (_path_cache.skiv_screenshots); @@ -101,75 +123,77 @@ SKIF_UI_Tab_DrawSettings (void) } } + ImGui::Spacing (); + + static std::vector + modes = { + { CaptureMode_Window, "Window", "###ModeToggle-Window", ((_registry.eScreenshotsAutosave & CaptureMode_Window) == CaptureMode_Window), &kbCaptureWindow }, + { CaptureMode_Region, "Region", "###ModeToggle-Region", ((_registry.eScreenshotsAutosave & CaptureMode_Region) == CaptureMode_Region), &kbCaptureRegion }, + { CaptureMode_Screen, "Screen", "###ModeToggle-Screen", ((_registry.eScreenshotsAutosave & CaptureMode_Screen) == CaptureMode_Screen), &kbCaptureScreen } + }; + + ImGui::TextColored ( + ImGui::GetStyleColorVec4(ImGuiCol_SKIF_TextCaption), + "Capture Mode:" + ); ImGui::SameLine ( ); - ImGui::Spacing ( ); + ImGui::ItemSize (ImVec2 (ImGui::GetFrameHeight (), ImGui::GetFrameHeight ()), ImGui::GetStyle().FramePadding.y); ImGui::SameLine ( ); - ImGui::Text ("%s", _path_cache.skiv_screenshotsA); - - if (! _registry.bSaveScreenshots) - SKIF_ImGui_PopDisableState ( ); - - ImGui::TreePop ( ); - } - - ImGui::Spacing (); - ImGui::Spacing (); - -#pragma endregion + float col2 = ImGui::GetCursorPosX (); + ImGui::TextColored (ImGui::GetStyleColorVec4(ImGuiCol_SKIF_Info), " " ICON_FA_FLOPPY_DISK); + SKIF_ImGui_SetHoverTip ("Save screenshot on disk"); + ImGui::SameLine ( ); + ImGui::ItemSize (ImVec2 (ImGui::GetFrameHeight (), ImGui::GetFrameHeight ()), ImGui::GetStyle().FramePadding.y); + ImGui::SameLine ( ); -#pragma region Section: Keybindings + float col3 = ImGui::GetCursorPosX (); - if (ImGui::CollapsingHeader ("Keybindings###SKIF_SettingsHeader-1", ImGuiTreeNodeFlags_DefaultOpen)) - { - SKIF_ImGui_Spacing ( ); + ImGui::TextColored ( + ImGui::GetStyleColorVec4(ImGuiCol_SKIF_TextCaption), + "Keybinding: " ICON_FA_KEYBOARD + ); - struct kb_kv_s + ImGui::TreePush ("CaptureModes"); + + ImGui::BeginGroup (); + for (auto& mode : modes) { - SK_KeybindMultiState* _key; - SKIF_RegistrySettings::KeyValue * _reg; - std::function _callback; - std::function _show; - }; + ImGui::ItemSize (ImVec2 (0.0f, ImGui::GetFrameHeight ()), ImGui::GetStyle().FramePadding.y); + ImGui::SameLine (); + ImGui::Text ( "%s", + mode._label); - static std::vector - keybinds = { - { &_registry.kbCaptureWindow, &_registry.regKVHotkeyCaptureWindow, { [](SK_KeybindMultiState* ptr) { SKIF_Util_RegisterHotKeyCapture (CaptureMode_Window, ptr->getKeybind()); } }, { []() { return true; } } }, - { &_registry.kbCaptureRegion, &_registry.regKVHotkeyCaptureRegion, { [](SK_KeybindMultiState* ptr) { SKIF_Util_RegisterHotKeyCapture (CaptureMode_Region, ptr->getKeybind()); } }, { []() { return true; } } }, - { &_registry.kbCaptureScreen, &_registry.regKVHotkeyCaptureScreen, { [](SK_KeybindMultiState* ptr) { SKIF_Util_RegisterHotKeyCapture (CaptureMode_Screen, ptr->getKeybind()); } }, { []() { return true; } } }, - { &_registry.kbToggleHDRDisplay, &_registry.regKVHotkeyToggleHDRDisplay, { [](SK_KeybindMultiState* ptr) { SKIF_Util_RegisterHotKeyHDRToggle (ptr->getKeybind() ); } }, { []() { return (SKIF_Util_IsWindows10v1709OrGreater ( ) && SKIF_Util_IsHDRSupported (NULL)); } } } - }; + ImGui::SameLine (); + ImGui::SetCursorPosX (col2); - ImGui::BeginGroup (); - for (auto& keybind : keybinds) - { - if (! keybind._show()) - continue; + if (ImGui::Checkbox (mode._unique_id, &mode._value)) + { + if (mode._value) + _registry.eScreenshotsAutosave |= mode._type; + else + _registry.eScreenshotsAutosave &= ~mode._type; - ImGui::Text ( "%s: ", - keybind._key->bind_name ); - ImGui::Spacing (); - } - ImGui::EndGroup (); - ImGui::SameLine (); - ImGui::BeginGroup (); - for (auto& keybind : keybinds) - { - if (! keybind._show()) - continue; + _registry.regKVScreenshotsAutosave.putData (_registry.eScreenshotsAutosave); + } - if (SK_ImGui_Keybinding (keybind._key)) + ImGui::SameLine (); + ImGui::SetCursorPosX (col3); + + if (SK_ImGui_Keybinding (mode._keybind->_key)) { // Only update the registry if we are done assigning - if (! keybind._key->assigning) - keybind._reg->putData (keybind._key->saved.human_readable); + if (! mode._keybind->_key->assigning) + mode._keybind->_reg->putData (mode._keybind->_key->saved.human_readable); - keybind._callback (keybind._key); + mode._keybind->_callback (mode._keybind->_key); } - - ImGui::Spacing (); } ImGui::EndGroup (); + + ImGui::TreePop ( ); + + ImGui::PopStyleColor (); } ImGui::Spacing (); @@ -179,12 +203,8 @@ SKIF_UI_Tab_DrawSettings (void) #pragma region Section: Image - if (ImGui::CollapsingHeader ("Images###SKIF_SettingsHeader-2", ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::CollapsingHeader ("Images###SKIF_SettingsHeader-1")) { - ImGui::PushStyleColor ( - ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_TextBase) - ); - SKIF_ImGui_Spacing ( ); #if 0 @@ -300,7 +320,7 @@ SKIF_UI_Tab_DrawSettings (void) #pragma region Section: Appearances - if (ImGui::CollapsingHeader ("Appearance###SKIF_SettingsHeader-3", ImGuiTreeNodeFlags_DefaultOpen)) + if (ImGui::CollapsingHeader ("Appearance###SKIF_SettingsHeader-2")) { ImGui::PushStyleColor ( ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_TextBase) @@ -684,6 +704,62 @@ SKIF_UI_Tab_DrawSettings (void) ImGui::Spacing (); #pragma endregion +#pragma region Section: Keybindings + + if (ImGui::CollapsingHeader ("Keybindings###SKIF_SettingsHeader-3", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::PushStyleColor ( + ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_TextBase) + ); + SKIF_ImGui_Spacing ( ); + + static std::vector + keybinds = { + &kbToggleHDRDisplay, + &kbCaptureWindow, + &kbCaptureRegion, + &kbCaptureScreen + }; + + ImGui::BeginGroup (); + for (auto& keybind : keybinds) + { + if (! keybind->_show()) + continue; + + ImGui::Text ( "%s: ", + keybind->_key->bind_name ); + ImGui::Spacing (); + } + ImGui::EndGroup (); + ImGui::SameLine (); + ImGui::BeginGroup (); + for (auto& keybind : keybinds) + { + if (! keybind->_show()) + continue; + + // TODO: Fix bug that causes the hotkey to remain unregistered if SKIV loses focus while the keybind dialog is visible + if (SK_ImGui_Keybinding (keybind->_key)) + { + // Only update the registry if we are done assigning + if (! keybind->_key->assigning) + keybind->_reg->putData (keybind->_key->saved.human_readable); + + keybind->_callback (keybind->_key); + } + + ImGui::Spacing (); + } + ImGui::EndGroup (); + ImGui::PopStyleColor (); + } + + ImGui::Spacing (); + ImGui::Spacing (); + +#pragma endregion + #pragma region Section: Advanced if (ImGui::CollapsingHeader ("Advanced###SKIF_SettingsHeader-4", ImGuiTreeNodeFlags_DefaultOpen)) { diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 0ddb5b7..ab23801 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -2725,7 +2725,7 @@ SKIF_UI_Tab_DrawViewer (void) SUCCEEDED (DirectX::CopyRectangle (*captured_img.GetImages (), src_rect, *subrect.GetImages (), DirectX::TEX_FILTER_DEFAULT, 0, 0))) { - if (SKIV_Image_CopyToClipboard (subrect.GetImages (), cover.is_hdr, true, L"cp_rect")) + if (SKIV_Image_CopyToClipboard (subrect.GetImages (), cover.is_hdr, CaptureMode_None, L"cp_rect")) { ImGui::InsertNotification ( { @@ -2760,7 +2760,7 @@ SKIF_UI_Tab_DrawViewer (void) else { - if (SKIV_Image_CopyToClipboard (captured_img.GetImages (), cover.is_hdr, true, L"cp_full")) + if (SKIV_Image_CopyToClipboard (captured_img.GetImages (), cover.is_hdr, CaptureMode_None, L"cp_full")) { ImGui::InsertNotification ( { diff --git a/src/utility/image.cpp b/src/utility/image.cpp index 088eaac..eabab4c 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -1078,7 +1078,7 @@ SKIV_PNG_CopyToClipboard (const DirectX::Image& image, const void *pData, size_t return false; } -bool SKIV_Image_CopyToClipboard (const DirectX::Image* pImage, bool isHDR, bool isTemp, const wchar_t* wszFileName) +bool SKIV_Image_CopyToClipboard (const DirectX::Image* pImage, bool isHDR, CaptureMode mode, const wchar_t* wszFileName) { using namespace DirectX; @@ -1087,7 +1087,8 @@ using namespace DirectX; static SKIF_CommonPathsCache& _path_cache = SKIF_CommonPathsCache::GetInstance ( ); - std::wstring wsPNGPath = (isTemp) ? _path_cache.skiv_temp : _path_cache.skiv_screenshots; + bool isPersistent = (mode != CaptureMode_None); + std::wstring wsPNGPath = (isPersistent) ? _path_cache.skiv_screenshots : _path_cache.skiv_temp; std::wstring wsFilename = std::wstring (wszFileName); wsFilename += L"_"; @@ -1183,7 +1184,7 @@ using namespace DirectX; PLOG_INFO << "SKIV_Image_TonemapToSDR ( ): FAILED!"; } - if (_registry.bSaveScreenshots) + if (isPersistent) { if (SUCCEEDED (SKIV_Image_SaveToDisk_SDR (*pImage, wsPNGPath.c_str(), false))) PLOG_VERBOSE << "SKIV_Image_SaveToDisk_SDR ( ): SUCCEEDED!"; @@ -3516,7 +3517,7 @@ SKIV_Image_CaptureRegion (SKIV_Region capture_area) PLOG_VERBOSE << "DirectX::FlipRotate ( ): FAILED"; } - if (SKIV_Image_CopyToClipboard (final, SKIV_DesktopImage._hdr_image, false, capture_area._title.c_str())) + if (SKIV_Image_CopyToClipboard (final, SKIV_DesktopImage._hdr_image, capture_area._mode, capture_area._title.c_str())) { PLOG_VERBOSE << "SKIV_Image_CopyToClipboard ( ): SUCCEEDED"; diff --git a/src/utility/registry.cpp b/src/utility/registry.cpp index 7059c63..52866cf 100644 --- a/src/utility/registry.cpp +++ b/src/utility/registry.cpp @@ -369,6 +369,9 @@ SKIF_RegistrySettings::SKIF_RegistrySettings (void) if (regKVCheckForUpdates.hasData(&hKey)) iCheckForUpdates = regKVCheckForUpdates .getData (&hKey); + if (regKVScreenshotsAutosave.hasData(&hKey)) + eScreenshotsAutosave = regKVScreenshotsAutosave .getData (&hKey); + if (regKVIgnoreUpdate.hasData(&hKey)) wsIgnoreUpdate = regKVIgnoreUpdate .getData (&hKey); @@ -400,9 +403,6 @@ SKIF_RegistrySettings::SKIF_RegistrySettings (void) if (regKV99thPercentileMaxCLL.hasData(&hKey)) b99thPercentileMaxCLL = regKV99thPercentileMaxCLL .getData (&hKey); - if (regKVSaveScreenshots.hasData(&hKey)) - bSaveScreenshots = regKVSaveScreenshots .getData (&hKey); - // These defaults to false, so no need to check if the registry has another value // since getData ( ) defaults to false for non-existent registry values bFirstLaunch = regKVFirstLaunch .getData (&hKey); @@ -419,13 +419,13 @@ SKIF_RegistrySettings::SKIF_RegistrySettings (void) // then parse the human_readable data through .parse() if (regKVHotkeyCaptureWindow.hasData(&hKey)) - kbCaptureWindow.pending.human_readable = regKVHotkeyCaptureWindow.getData (&hKey); + kbCaptureWindow.pending.human_readable = regKVHotkeyCaptureWindow .getData (&hKey); if (regKVHotkeyCaptureRegion.hasData(&hKey)) - kbCaptureRegion.pending.human_readable = regKVHotkeyCaptureRegion.getData (&hKey); + kbCaptureRegion.pending.human_readable = regKVHotkeyCaptureRegion .getData (&hKey); if (regKVHotkeyCaptureScreen.hasData(&hKey)) - kbCaptureScreen.pending.human_readable = regKVHotkeyCaptureScreen.getData (&hKey); + kbCaptureScreen.pending.human_readable = regKVHotkeyCaptureScreen .getData (&hKey); if (regKVHotkeyToggleHDRDisplay.hasData(&hKey)) kbToggleHDRDisplay.pending.human_readable = regKVHotkeyToggleHDRDisplay.getData (&hKey); diff --git a/src/utility/sk_utility.cpp b/src/utility/sk_utility.cpp index a6e077b..fe60511 100644 --- a/src/utility/sk_utility.cpp +++ b/src/utility/sk_utility.cpp @@ -425,6 +425,14 @@ SK_Keybind::parse (void) shift = false; super = false; + // Abort if the key is unbound + if (human_readable == L"") + { + human_readable_utf8 = SK_WideCharToUTF8 (human_readable); + masked_code = 1 << 13; // Forced invalid to apply changes on launch + return; + } + wchar_t wszKeyBind [128] = { }; lstrcatW (wszKeyBind, human_readable.c_str ()); @@ -781,6 +789,8 @@ SK_ImGui_KeybindDialog (SK_KeybindMultiState* keybind) // Render over all other windows //ImGui::SetNextWindowFocus ( ); + + g_activeKeybindPopup = true; } if (ImGui::BeginPopupModal (keybind->bind_name, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_Tooltip | // ImGuiWindowFlags_Tooltip is required to work around a pesky z-order issue on first appearance @@ -837,13 +847,15 @@ SK_ImGui_KeybindDialog (SK_KeybindMultiState* keybind) bool bEscape = ImGui::IsKeyPressed (ImGuiKey_Escape, false), bBackspace = - ImGui::IsKeyPressed (ImGuiKey_Backspace, false); + ImGui::IsKeyPressed (ImGuiKey_Backspace, false), + bDelete = + ImGui::IsKeyPressed (ImGuiKey_Delete, false); ImGui::Text ("Keybinding:"); // %hs, keybind->pending.human_readable_utf8.c_str ()); // (0x%02X), keybind->vKey ImGui::SameLine ( ); ImGui::TextColored (ImGui::GetStyleColorVec4 (ImGuiCol_SKIF_TextBase), keybind->pending.human_readable_utf8.c_str ()); ImGui::Separator ( ); - ImGui::TextDisabled ("Press BACKSPACE to clear, or ESC to finish."); + ImGui::TextDisabled ("Press BACKSPACE to clear, DELETE to reset to default, or ESC to finish."); // Update the key binding after printing out the current one, to prevent a one-frame graphics glitch if (bBackspace) @@ -857,6 +869,14 @@ SK_ImGui_KeybindDialog (SK_KeybindMultiState* keybind) keybind->pending.update ( ); } + else if (bDelete) + { + keybind->pending = keybind->default; + keybind->pending.parse ( ); + keybind->pending.makeMask ( ); + keybind->pending.update ( ); + } + else if (! bEscape && vKey != 256) { keybind->pending.vKey = static_cast (vKey); @@ -870,7 +890,7 @@ SK_ImGui_KeybindDialog (SK_KeybindMultiState* keybind) } // If we are done with the changes, mark it as such - if (bEscape || bBackspace) + if (bEscape || bBackspace || bDelete) { keybind->assigning = false; ImGui::CloseCurrentPopup ( ); @@ -892,9 +912,16 @@ SK_ImGui_Keybinding (SK_KeybindMultiState* binding) if (! binding) return false; + ImGui::PushID (binding->bind_name); + if (SK_ImGui_KeybindSelect (&binding->saved)) - ImGui::OpenPopup ( binding->bind_name); + ImGui::OpenPopup (binding->bind_name); + + bool results = + SK_ImGui_KeybindDialog (binding); + + ImGui::PopID (); - return SK_ImGui_KeybindDialog (binding); + return results; } ; \ No newline at end of file diff --git a/src/utility/utility.cpp b/src/utility/utility.cpp index d272634..b12f913 100644 --- a/src/utility/utility.cpp +++ b/src/utility/utility.cpp @@ -30,6 +30,8 @@ UINT CF_HTML = NULL; +bool g_activeKeybindPopup = false; + std::vector vWatchHandles[UITab_ALL]; INT64 SKIF_TimeInMilliseconds = 0; From 262eba5eb21f8f3e47c56d1ac7d613da98308ceb Mon Sep 17 00:00:00 2001 From: Aemony Date: Sun, 22 Mar 2026 15:11:30 +0100 Subject: [PATCH 02/23] Sort from File Explorer + Delete + menu changes - SKIV now defaults to using the same sort order as File Explorer. - Added Delete functionality to the app - Rearranged some of the context menu items -- Removed icons for some seldom used actions to improve UX -- Moved Encoder Setting further down -- Separated Copy/Close/Delete actions from the top Open/Save actions Known bugs: - File Explorer sort is retained for network shares if deleting a file - Minor focus loss bug occurs if the "Confirm Delete" warning is shown --- include/tabs/viewer.h | 2 +- include/utility/image.h | 92 ++++++++ include/utility/utility.h | 3 +- src/tabs/settings.cpp | 2 +- src/tabs/viewer.cpp | 190 +++++---------- src/utility/image.cpp | 477 ++++++++++++++++++++++++++++++++++++++ src/utility/utility.cpp | 41 +++- 7 files changed, 663 insertions(+), 144 deletions(-) diff --git a/include/tabs/viewer.h b/include/tabs/viewer.h index 1bcd755..140ddd0 100644 --- a/include/tabs/viewer.h +++ b/include/tabs/viewer.h @@ -64,4 +64,4 @@ enum ImageScaling { }; uint32_t SKIV_Viewer_CycleVisualizationModes (void); -ImageScaling SKIV_Viewer_CycleScalingModes (void); \ No newline at end of file +ImageScaling SKIV_Viewer_CycleScalingModes (void); diff --git a/include/utility/image.h b/include/utility/image.h index 21cdf2c..f480d55 100644 --- a/include/utility/image.h +++ b/include/utility/image.h @@ -280,4 +280,96 @@ struct skiv_image_desktop_s { _max_display_nits = 1000.0f; _rotation = DXGI_MODE_ROTATION_UNSPECIFIED; } +}; + +// Image Directory + +#include + +struct skiv_image_directory_s { + + class FileSystemBindData : public IFileSystemBindData + { + public: + FileSystemBindData() : _ref(1) + { + ZeroMemory(&_fd, sizeof(_fd)); + } + + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) override + { + if (riid == IID_IUnknown || riid == IID_IFileSystemBindData) + { + *ppv = static_cast(this); + AddRef(); + return S_OK; + } + *ppv = nullptr; + return E_NOINTERFACE; + } + + IFACEMETHODIMP_(ULONG) AddRef() override + { + return InterlockedIncrement(&_ref); + } + + IFACEMETHODIMP_(ULONG) Release() override + { + ULONG r = InterlockedDecrement(&_ref); + if (r == 0) delete this; + return r; + } + + // IFileSystemBindData + IFACEMETHODIMP SetFindData (const WIN32_FIND_DATAW* pfd) override + { + _fd = *pfd; + return S_OK; + } + + IFACEMETHODIMP GetFindData (WIN32_FIND_DATAW* pfd) override + { + *pfd = _fd; + return S_OK; + } + + private: + ~FileSystemBindData() = default; + + LONG _ref; + WIN32_FIND_DATAW _fd; + }; + + struct fd_s { + std::wstring filename; // Image filename + //std::wstring folder_path; // Parent folder path + std::wstring path; // Image path (full) + WIN32_FIND_DATA ffd; + }; + +//std::wstring orig_path; // Holds a cached copy of cover.path +//std::wstring filename; // Image filename + std::wstring folder_path; // Parent folder path + SKIF_DirectoryWatch watch; + std::vector fileList; + std::vector::iterator activeFile; + std::vector sortColumns; // File Explorer + + void reset (void); + void setImage (const std::wstring& path); + std::wstring nextImage (void); + std::wstring prevImage (void); + std::wstring deleteImage (void); + + // Find the position of the image in the current folder + void updateFileIterator (const std::wstring& path); + + // Retrieve all files in the folder, and identify our current place among them... + void updateFolderData (void); + + // Win32 File Explorer based sorting + bool sortByColumns (void); + void sortByFilename (void); + bool updateSortColumns (void); // Returns true if the sort columns have changed }; \ No newline at end of file diff --git a/include/utility/utility.h b/include/utility/utility.h index d06d927..4b4d81d 100644 --- a/include/utility/utility.h +++ b/include/utility/utility.h @@ -225,7 +225,8 @@ std::wstring SKIF_Util_GetClipboardHDROP (void); DirectX::Image SKIF_Util_GetClipboardBitmapData (void); std::wstring SKIF_Util_AddEnvironmentBlock (const void* pEnvBlock, const std::wstring& varName, const std::wstring& varValue); void SKIF_Util_FileExplorer_SelectFile (PCWSTR filePath); -std::wstring SKIF_Util_FileExplorer_BrowseFolder (PCWSTR defaultPath); +bool SKIF_Util_FileExplorer_DeleteFile (PCWSTR filePath, bool hideWarning); +std::wstring SKIF_Util_FileExplorer_BrowseForFolder(PCWSTR defaultPath); std::string SKIF_Util_GetWindowMessageAsStr (UINT msg); diff --git a/src/tabs/settings.cpp b/src/tabs/settings.cpp index b89b6b6..847261b 100644 --- a/src/tabs/settings.cpp +++ b/src/tabs/settings.cpp @@ -105,7 +105,7 @@ SKIF_UI_Tab_DrawSettings (void) ImGui::SameLine ( ); if (ImGui::Selectable(_path_cache.skiv_screenshotsA)) { - std::wstring newPath = SKIF_Util_FileExplorer_BrowseFolder (_path_cache.skiv_screenshots); + std::wstring newPath = SKIF_Util_FileExplorer_BrowseForFolder (_path_cache.skiv_screenshots); if (PathFileExistsW (newPath.c_str())) { diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index ab23801..6828636 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include @@ -89,7 +88,6 @@ #pragma comment (lib, "dxguid.lib") - ImRect copyRect = { 0,0,0,0 }; bool wantCopyToClipboard = false; @@ -178,6 +176,7 @@ const std::initializer_list supported_hdr_encode_formats = //FileSignature { L"image/vnd-ms.dds", { L".dds" }, { 0x44, 0x44, 0x53, 0x20 } }, }; + bool isJXLDecoderAvailable (void) { static HMODULE hModBrotliCommon = nullptr; @@ -2684,6 +2683,9 @@ SKIF_UI_Tab_DrawViewer (void) { extern bool imageFadeActive; + // Used to monitor the current image folder + static skiv_image_directory_s _current_folder; + // ** Move this code somewhere more sensible // // User is requesting to copy the loaded image to clipboard, @@ -2787,7 +2789,6 @@ SKIF_UI_Tab_DrawViewer (void) } } - auto _SwapOutCover = [&](void) -> void { // Hide the current cover and set it up to be unloaded @@ -2828,6 +2829,20 @@ SKIF_UI_Tab_DrawViewer (void) } }; + auto _DeleteImage = [&](void) -> void + { + //DeleteFile (_current_folder.activeFile->path.c_str()); + + // TODO: Fix focus loss bug when a warning is shown... + if (SKIF_Util_FileExplorer_DeleteFile (_current_folder.activeFile->path.c_str(), true)) + { + dragDroppedFilePath = _current_folder.deleteImage ( ); + + if (dragDroppedFilePath.empty()) + _SwapOutCover (); + } + }; + #pragma region Initialization SK_RunOnce (fAlpha = (_registry.bFadeCovers) ? 0.0f : 1.0f); @@ -2901,115 +2916,6 @@ SKIF_UI_Tab_DrawViewer (void) ::SetWindowText (SKIF_ImGui_hWnd, (cover.file_info.filename + L" - " + SKIV_WINDOW_TITLE_SHORT_W).c_str()); } - // Monitor the current image folder - struct { - std::wstring orig_path; // Holds a cached copy of cover.path - std::wstring filename; // Image filename - std::wstring path; // Parent folder path - std:: string path_utf8; - SKIF_DirectoryWatch watch; - std::vector fileList; - unsigned int fileListIndex = 0; - - void reset (void) - { - PLOG_VERBOSE << "reset _current_folder!"; - - orig_path.clear(); - filename.clear(); - path.clear(); - path_utf8.clear(); - fileList.clear(); - fileListIndex = 0; - watch.reset(); - } - - std::wstring nextImage (void) - { - if (fileList.size() == 0 || fileListIndex == fileList.size() - 1) - return L""; - - fileListIndex++; - fileListIndex %= fileList.size(); - return (path + LR"(\)" + fileList[fileListIndex]); - } - - std::wstring prevImage (void) - { - if (fileList.size() == 0 || fileListIndex == 0) - return L""; - - fileListIndex--; - fileListIndex %= fileList.size(); - return (path + LR"(\)" + fileList[fileListIndex]); - } - - // Find the position of the image in the current folder - void findFileIndex (void) - { - // Set the index to the proper position - fileListIndex = 0; - for (auto& file : fileList) - { - if (filename != file) - fileListIndex++; - else - break; - } - } - - // Retrieve all files in the folder, and identify our current place among them... - void updateFolderData (void) - { - HANDLE hFind = INVALID_HANDLE_VALUE; - WIN32_FIND_DATA ffd = { }; - fileList.clear(); - - PLOG_DEBUG << "Discovering ... " << (path + LR"(\*.*)"); - - hFind = - FindFirstFileExW ((path + LR"(\*.*)").c_str(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH); - - if (INVALID_HANDLE_VALUE != hFind) - { - fileList.push_back (ffd.cFileName); - - while (FindNextFile (hFind, &ffd)) - fileList.push_back (ffd.cFileName); - - FindClose (hFind); - } - - if (! fileList.empty()) - { - std::vector filtered; - - // Filter out unsupported file formats using their file extension - for (auto& file : fileList) - if (isExtensionSupported (std::filesystem::path(file).extension().wstring())) - filtered.push_back (file); - - fileList = filtered; - - if (! fileList.empty()) - { - std::sort (fileList.begin(), - fileList.end (), - []( const std::wstring& a, - const std::wstring& b ) -> int - { - return StrCmpLogicalW (a.c_str(), b.c_str()) < 0; - } - ); - - findFileIndex ( ); - } - } - - PLOG_DEBUG << "Found " << fileList.size() << " supported images in the folder."; - } - } static _current_folder; - // Do not clear when we are loading an image (so as to not process the same folder constantly) if (! loadImage && ! tryingToLoadImage) { @@ -3018,38 +2924,39 @@ SKIF_UI_Tab_DrawViewer (void) // Identify when an image has been closed if (cover.file_info.path.empty()) { - if (! _current_folder.path.empty()) + if (! _current_folder.folder_path.empty()) _current_folder.reset(); } // Identify when we're dealing with a whole new folder - if (cover.file_info.folder_path != _current_folder.path) + if (cover.file_info.folder_path != _current_folder.folder_path) { _current_folder.reset(); - _current_folder.orig_path = cover.file_info.path; - std::filesystem::path path = SKIF_Util_NormalizeFullPath (cover.file_info.path); - _current_folder.filename = path.filename().wstring(); - _current_folder.path = path.parent_path().wstring(); - _current_folder.path_utf8 = SK_WideCharToUTF8 (_current_folder.path); + //_current_folder.orig_path = cover.file_info.path; + std::filesystem::path path = SKIF_Util_NormalizeFullPath (cover.file_info.path); + //_current_folder.filename = path.filename().wstring(); + _current_folder.folder_path = path.parent_path().wstring(); - PLOG_VERBOSE << "Watching the folder... " << _current_folder.path; + PLOG_VERBOSE << "Watching the folder... " << _current_folder.folder_path; // This triggers a new updateFolderData() run below dwLastSignaled = 1; } // Identify when a new file from the same folder has been dropped - if (cover.file_info.path != _current_folder.orig_path) + if (! _current_folder.fileList.empty() && + cover.file_info.filename != _current_folder.activeFile->filename) { - _current_folder.orig_path = cover.file_info.path; - std::filesystem::path path = SKIF_Util_NormalizeFullPath (cover.file_info.path); - _current_folder.filename = path.filename().wstring(); - _current_folder.findFileIndex ( ); + // Re-sort the folder if the sort columns have changed + if (_current_folder.updateSortColumns ( )) + _current_folder.sortByColumns ( ); + + _current_folder.updateFileIterator (cover.file_info.path); } // Identify when the folder was changed outside of the app - if (_current_folder.watch.isSignaled (_current_folder.path)) + if (_current_folder.watch.isSignaled (_current_folder.folder_path)) { dwLastSignaled = SKIF_Util_timeGetTime(); PLOG_VERBOSE << "_current_folder.watch was signaled! Delay checking the folder for another 500ms..."; @@ -3057,7 +2964,8 @@ SKIF_UI_Tab_DrawViewer (void) if (dwLastSignaled != 0 && dwLastSignaled + 500 < SKIF_Util_timeGetTime()) { - _current_folder.updateFolderData(); + _current_folder.updateFolderData ( ); + _current_folder.updateFileIterator (cover.file_info.path); dwLastSignaled = 0; } } @@ -3065,6 +2973,9 @@ SKIF_UI_Tab_DrawViewer (void) // Only apply changes to the scaling method if we actually have an image loaded if (cover.pRawTexSRV.p != nullptr) { + if (ImGui::GetKeyData (ImGuiKey_Delete)->DownDuration == 0.0f) // Delete - Delete the opened image + _DeleteImage (); + // These keybindings requires Ctrl to be held down if (ImGui::GetIO().KeyCtrl) { @@ -4072,15 +3983,15 @@ SKIF_UI_Tab_DrawViewer (void) if (cover.is_hdr && SKIF_ImGui_MenuItemEx2 ("Export to SDR", ICON_FA_FILE_EXPORT, ImGui::GetStyleColorVec4(ImGuiCol_Text), "Ctrl+X")) ExportSDRDialog = PopupState_Open; - if (SKIF_ImGui_MenuItemEx2 ("Encoder Setup", ICON_FA_GEARS, ImGui::GetStyleColorVec4(ImGuiCol_Text), "Ctrl+B")) - ConfigEncoders = PopupState_Open; - if (//cover.is_hdr && - SKIF_ImGui_MenuItemEx2 ("Copy", ICON_FA_CLIPBOARD, ImGui::GetStyleColorVec4(ImGuiCol_Text), "Ctrl+C")) - { + + ImGui::Separator ( ); + + if (SKIF_ImGui_MenuItemEx2 ("Copy", ICON_FA_CLIPBOARD, ImGui::GetStyleColorVec4(ImGuiCol_Text), "Ctrl+C")) wantCopyToClipboard = true; - } - //if (SKIF_ImGui_MenuItemEx2 ("Close", 0, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_Info), "Ctrl+W")) - // _SwapOutCover (); + if (SKIF_ImGui_MenuItemEx2 ("Close", 0, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_Info), "Ctrl+W")) + _SwapOutCover (); + if (SKIF_ImGui_MenuItemEx2 ("Delete", ICON_FA_TRASH_CAN, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_Failure), "Delete")) + _DeleteImage (); // Image scaling @@ -4088,7 +3999,7 @@ SKIF_UI_Tab_DrawViewer (void) ImGui::PushID ("#ImageScaling"); - if (SKIF_ImGui_BeginMenuEx2 ("Scaling", ICON_FA_PANORAMA)) + if (SKIF_ImGui_BeginMenuEx2 ("Scaling", 0)) // ICON_FA_PANORAMA { auto _CreateMenuItem = [&](ImageScaling image_scaling, const char* label, const char* shortcut) { bool bEnabled = (cover.scaling == image_scaling); @@ -4169,9 +4080,12 @@ SKIF_UI_Tab_DrawViewer (void) ImGui::PopID ( ); // #HDRVisualization } - if (SKIF_ImGui_MenuItemEx2 ("Details", ICON_FA_BARCODE, ImGui::GetStyleColorVec4 (ImGuiCol_Text), "Ctrl+D", &_registry.bImageDetails)) + if (SKIF_ImGui_MenuItemEx2 ("Details", 0, ImGui::GetStyleColorVec4 (ImGuiCol_Text), "Ctrl+D", &_registry.bImageDetails)) // ICON_FA_BARCODE _registry.regKVImageDetails.putData (_registry.bImageDetails); + if (SKIF_ImGui_MenuItemEx2 ("Encoder Setup", 0, ImGui::GetStyleColorVec4(ImGuiCol_Text), "Ctrl+B")) // ICON_FA_GEARS + ConfigEncoders = PopupState_Open; + ImGui::Separator ( ); if (! cover.file_info.path.empty() && SKIF_ImGui_MenuItemEx2 ("Browse Folder", ICON_FA_FOLDER_OPEN, ImColor(255, 207, 72), "Ctrl+E")) @@ -4180,7 +4094,7 @@ SKIF_UI_Tab_DrawViewer (void) ImGui::Separator ( ); - if (SKIF_ImGui_MenuItemEx2 ("Settings", ICON_FA_LIST_CHECK)) + if (SKIF_ImGui_MenuItemEx2 ("Settings", 0)) // ICON_FA_LIST_CHECK SKIF_Tab_ChangeTo = UITab_Settings; ImGui::Separator ( ); diff --git a/src/utility/image.cpp b/src/utility/image.cpp index eabab4c..165878e 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -3958,4 +3958,481 @@ void sk_avif_add_icc_to_image (avifImage* img) SK_avifImageSetProfileICC ( img, RGB_D65_202_Rel_PeQ, sizeof (RGB_D65_202_Rel_PeQ) ); +} + + +// Image Directory +void +skiv_image_directory_s::reset (void) +{ + PLOG_VERBOSE << "reset _current_folder!"; + + //orig_path.clear(); + //filename.clear(); + folder_path.clear(); + fileList.clear(); + activeFile = fileList.begin(); + //fileListIndex = 0; + watch.reset(); +} + +void +skiv_image_directory_s::setImage (const std::wstring& path) +{ + if (fileList.empty()) + return; + + activeFile = std::find_if (fileList.begin(), fileList.end(), [&](const fd_s& file) { return file.path == path; }); + + //fileListIndex = 0; + /* + for (auto& file : fileList) + { + if (file.path == path) + { + activeFile = std::next (fileList.begin(), fileListIndex); + return; + } + } + */ +} + +std::wstring +skiv_image_directory_s::nextImage (void) +{ + if (fileList.empty() || activeFile == fileList.end()) + return L""; + + std::advance (activeFile, 1); + return activeFile->path; + + //fileListIndex++; + //fileListIndex %= fileList.size(); + //return (fileList[fileListIndex].path); +} + +std::wstring +skiv_image_directory_s::prevImage (void) +{ + if (fileList.empty() || activeFile == fileList.begin()) + return L""; + + std::advance (activeFile, -1); + return activeFile->path; + + //fileListIndex--; + //fileListIndex %= fileList.size(); + //return (fileList[fileListIndex].path); +} + +std::wstring +skiv_image_directory_s::deleteImage (void) +{ + if (fileList.empty()) + return L""; + + activeFile = fileList.erase (activeFile); + + // Apparently erase() does not select the new populated end() ? Odd... + if (activeFile->path.empty() && ! fileList.empty()) + prevImage(); + + return activeFile->path; +} + +// Find the position of the image in the current folder +void +skiv_image_directory_s::updateFileIterator (const std::wstring& path) +{ + activeFile = std::find_if (fileList.begin(), fileList.end(), [&](const fd_s& file) { return file.path == path; }); + + /* + // Set the index to the proper position + fileListIndex = 0; + for (auto& file : fileList) + { + if (filename != file.name) + fileListIndex++; + else + break; + } + */ +} + +// Retrieve all files in the folder, and identify our current place among them... +void +skiv_image_directory_s::updateFolderData (void) +{ + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA ffd = { }; + fileList.clear(); + + PLOG_DEBUG << "Discovering ... " << (folder_path + LR"(\*.*)"); + + DWORD temp_time = SKIF_Util_timeGetTime1(); + + hFind = + FindFirstFileExW ((folder_path + LR"(\*.*)").c_str(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH); + + if (INVALID_HANDLE_VALUE != hFind) + { + fileList.push_back ({ ffd.cFileName, folder_path + LR"(\)" + ffd.cFileName, ffd }); + + while (FindNextFile (hFind, &ffd)) + fileList.push_back ({ ffd.cFileName, folder_path + LR"(\)" + ffd.cFileName, ffd }); + + FindClose (hFind); + } + + PLOG_DEBUG << "Operation [FindFirstFileExW/FindNextFile] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; + temp_time = SKIF_Util_timeGetTime1(); + + if (! fileList.empty()) + { + std::vector filtered; + extern bool isExtensionSupported (const std::wstring extension); + + // Filter out unsupported file formats using their file extension + for (auto& file : fileList) + if (isExtensionSupported (std::filesystem::path(file.filename).extension().wstring())) + filtered.push_back (file); + + fileList = filtered; + + if (! fileList.empty()) + { + temp_time = SKIF_Util_timeGetTime1(); + + // Let us try File Explorer sort first + if (updateSortColumns ( ) && sortByColumns ( )) { } + else sortByFilename ( ); + } + } + + PLOG_DEBUG << "Found " << fileList.size() << " supported images in the folder."; +} + + +#pragma comment(lib, "Propsys.lib") + +#include +#include +#include + +static std::wstring +PropertyKeyToString (const PROPERTYKEY& key) +{ + PWSTR pszName = nullptr; + + if (SUCCEEDED(PSGetNameFromPropertyKey(key, &pszName))) + { + std::wstring result(pszName); + CoTaskMemFree(pszName); + return result; + } + + return L"(unknown)"; +} + +static bool +GetPropertyValue (const std::wstring& path, + const PROPERTYKEY& key, + PROPVARIANT* pVar) +{ + CComPtr spStore; + + HRESULT hr = SHGetPropertyStoreFromParsingName( + path.c_str(), + NULL, + GPS_FASTPROPERTIESONLY, // GPS_FASTPROPERTIESONLY / GPS_DEFAULT + IID_PPV_ARGS (&spStore)); + + if (FAILED(hr)) + { + _com_error err(hr); + PLOG_ERROR << "Failed with path: " << path << ", error: " << SK_WideCharToUTF8(err.ErrorMessage()); + return false; + } + + return SUCCEEDED(spStore->GetValue(key, pVar)); +} + +static int +ComparePropVariants (const PROPVARIANT& a, const PROPVARIANT& b) +{ + return PropVariantCompare(a, b); +} + +static bool +GetFolderSortColumns (const std::wstring& path, std::vector& sortColumns) +{ + sortColumns.clear(); + + CComPtr spWindows; + if (FAILED (spWindows.CoCreateInstance (CLSID_ShellWindows))) + return false; + + long count = 0; + spWindows->get_Count (&count); + + struct c_s { + HWND hWnd = NULL; + std::vector sortColumns; + }; + + std::vector candidates; + + for (long i = 0; i < count; ++i) + { + CComVariant vtIndex(i); + CComPtr spDisp; + + if (FAILED (spWindows->Item (vtIndex, &spDisp)) || !spDisp) + continue; + + CComPtr spBrowser; + if (FAILED (spDisp->QueryInterface (IID_PPV_ARGS(&spBrowser)))) + continue; + + // Get location URL (file:///C:/...) + BSTR bstrURL; + if (FAILED (spBrowser->get_LocationURL (&bstrURL))) + continue; + + std::wstring url(bstrURL, SysStringLen (bstrURL)); + SysFreeString (bstrURL); + + // Convert URL → path + wchar_t wszPath[MAX_PATH]; + DWORD size = MAX_PATH; + if (FAILED (PathCreateFromUrlW (url.c_str(), wszPath, &size, 0))) + continue; + + // Compare with target path + if (_wcsicmp (wszPath, path.c_str()) != 0) + continue; + + // Found matching Explorer window + CComPtr spSP; + if (FAILED (spBrowser->QueryInterface (IID_PPV_ARGS(&spSP)))) + continue; + + CComPtr spShellBrowser; + if (FAILED (spSP->QueryService (SID_STopLevelBrowser, IID_PPV_ARGS(&spShellBrowser)))) + continue; + + CComPtr spView; + if (FAILED (spShellBrowser->QueryActiveShellView (&spView))) + continue; + + CComPtr spFV2; + if (FAILED (spView->QueryInterface (IID_PPV_ARGS(&spFV2)))) + continue; + + // We have a candidate + c_s item; + + // Retrieve the window HWND + spBrowser->get_HWND ((SHANDLE_PTR*)& item.hWnd); + + // Get sort columns + int sortColumnCount = 0; + if (SUCCEEDED (spFV2->GetSortColumnCount (&sortColumnCount))) + { + item.sortColumns = std::vector (sortColumnCount); + + if (SUCCEEDED (spFV2->GetSortColumns (item.sortColumns.data(), sortColumnCount))) + { + PLOG_VERBOSE << "Sort column count: " << sortColumnCount; + + for (int col = 0; col < sortColumnCount; ++col) + { + PROPERTYKEY key = item.sortColumns[col].propkey; + SORTDIRECTION direction = item.sortColumns[col].direction; + + PLOG_VERBOSE << "Column " << col << " [" << PropertyKeyToString(key) << "], direction: " << (direction == SORT_ASCENDING ? "ASC" : "DESC"); + } + + candidates.push_back (std::move (item)); + } + } + } + + if (! candidates.empty()) + { + // Walk downard through the Z-order + for (HWND wnd = GetWindow (candidates[0].hWnd, GW_HWNDFIRST); wnd != NULL; wnd = GetNextWindow (wnd, GW_HWNDNEXT)) + { + auto it = std::find_if (candidates.begin(), candidates.end(), [&](const c_s& c) { return c.hWnd == wnd; } ); + + if (it == candidates.end()) + continue; + + PLOG_VERBOSE << "Found sort columns!"; + sortColumns = it->sortColumns; + + /* + for (int col = 0; col < sortColumns.size(); ++col) + { + PROPERTYKEY key = sortColumns[col].propkey; + SORTDIRECTION direction = sortColumns[col].direction; + + PLOG_VERBOSE << "Column " << col << " [" << PropertyKeyToString(key) << "], direction: " << (direction == SORT_ASCENDING ? "ASC" : "DESC"); + } + */ + + return true; + } + } + + return false; +} + +bool +skiv_image_directory_s::updateSortColumns (void) +{ + std::vector oldSort = sortColumns; + if (! GetFolderSortColumns (folder_path, sortColumns)) + return true; + + if (sortColumns.size() != oldSort.size()) + return true; + + for (int i = 0; i < oldSort.size(); i++) + { + if ((oldSort[i].propkey != sortColumns[i].propkey) || + (oldSort[i].direction != sortColumns[i].direction)) + return true; + } + + return false; +} + +void +skiv_image_directory_s::sortByFilename (void) +{ + std::sort (fileList.begin(), + fileList.end (), + []( const fd_s& a, + const fd_s& b ) -> int + { + return StrCmpLogicalW (a.filename.c_str(), b.filename.c_str()) < 0; + } + ); + + PLOG_VERBOSE << "Sorted alphabetically!"; +} + +bool +skiv_image_directory_s::sortByColumns (void) +{ + if (sortColumns.empty()) + return false; + + struct cf_s + { + fd_s file; + std::vector values; + }; + std::vector cache; + + DWORD temp_time = SKIF_Util_timeGetTime1(); + DWORD sum_time_parsing = 0; + DWORD sum_time_getvalue = 0; + + // Cache the properties of all files + CComPtr bindCtx; + CComPtr spStore; + CComPtr spFSBD = new FileSystemBindData(); + + HRESULT hr = CreateBindCtx (0, &bindCtx); + + if (FAILED (hr)) + { + _com_error err(hr); + PLOG_ERROR << "Operation [CreateBindCtx] failed with error: " << SK_WideCharToUTF8 (err.ErrorMessage()); + return false; + } + + for (const auto& file : fileList) + { + cf_s item; + item.file = file; + item.values.resize (sortColumns.size()); + + DWORD tmp = SKIF_Util_timeGetTime1(); + + spFSBD->SetFindData(&file.ffd); // Always returns S_OK claims the docs + hr = bindCtx->RegisterObjectParam (STR_FILE_SYS_BIND_DATA, spFSBD); + + if (FAILED (hr)) + { + _com_error err(hr); + PLOG_ERROR << "Operation [RegisterObjectParam] failed with error: " << SK_WideCharToUTF8 (err.ErrorMessage()); + } + + hr = SHGetPropertyStoreFromParsingName (file.path.c_str(), bindCtx, GPS_FASTPROPERTIESONLY | GPS_BESTEFFORT | GPS_NO_OPLOCK, IID_PPV_ARGS(&spStore)); + sum_time_parsing += (SKIF_Util_timeGetTime1() - tmp); + + if (FAILED (hr)) + { + _com_error err(hr); + PLOG_ERROR << "Failed to retrieve properties for path: " << file.path; + PLOG_ERROR << "Error: " << SK_WideCharToUTF8 (err.ErrorMessage()); + continue; + } + + for (size_t i = 0; i < sortColumns.size(); ++i) + { + PropVariantInit (&item.values[i]); + DWORD tmp2 = SKIF_Util_timeGetTime1(); + spStore->GetValue (sortColumns[i].propkey, &item.values[i]); + sum_time_getvalue += (SKIF_Util_timeGetTime1() - tmp2); + } + + cache.push_back (std::move(item)); + } + + PLOG_DEBUG << "Operation [SHGetPropertyStoreFromParsingName] took " << sum_time_parsing << " ms."; + PLOG_DEBUG << "Operation [IPropertyStore::GetValue] took " << sum_time_getvalue << " ms."; + + temp_time = SKIF_Util_timeGetTime1(); + + std::sort (cache.begin(), cache.end(), [&](const cf_s& a, const cf_s& b) + { + for (size_t i = 0; i < sortColumns.size(); ++i) + { + int cmp = PropVariantCompare(a.values[i], b.values[i]); + + if (cmp != 0) + { + if (sortColumns[i].direction == SORT_DESCENDING) + cmp = -cmp; + + return cmp < 0; + } + } + return false; + } + ); + + PLOG_DEBUG << "Operation [SortItems] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; + temp_time = SKIF_Util_timeGetTime1(); + + fileList.clear(); + + for (auto& item : cache) + { + fileList.push_back (item.file); + + for (auto& v : item.values) + PropVariantClear (&v); + } + + PLOG_DEBUG << "Operation [Cleanup] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; + PLOG_DEBUG << "Operation took ~" << (sum_time_parsing + sum_time_getvalue) << " ms."; + + PLOG_VERBOSE << "Sorted based on File Explorer columns!"; + + return true; } \ No newline at end of file diff --git a/src/utility/utility.cpp b/src/utility/utility.cpp index b12f913..62c77a1 100644 --- a/src/utility/utility.cpp +++ b/src/utility/utility.cpp @@ -2702,9 +2702,42 @@ SKIF_Util_FileExplorer_SelectFile (PCWSTR filePath) delete data; } +bool +SKIF_Util_FileExplorer_DeleteFile (PCWSTR filePath, bool hideWarning) +{ + bool ret = false; + + IShellItem* psi = nullptr; + if (SUCCEEDED (SHCreateItemFromParsingName (filePath, nullptr, IID_PPV_ARGS(&psi)))) + { + IFileOperation* pfo = nullptr; + if (SUCCEEDED (CoCreateInstance (CLSID_FileOperation, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&pfo)))) + { + // This primarily adheres to Explorer's "Display delete confirmation dialog" setting + // i.e. if that setting is disabled, the confirmation won't appear regardless + DWORD flags = + FOF_FILESONLY | FOFX_RECYCLEONDELETE | FOFX_ADDUNDORECORD | ((hideWarning) ? FOF_NOCONFIRMATION : FOF_WANTNUKEWARNING); // FOF_WANTNUKEWARNING only seems to trigger on network shares + + if (SUCCEEDED (pfo->SetOperationFlags (flags))) + { + if (SUCCEEDED (pfo->DeleteItem (psi, nullptr))) + { + ret = SUCCEEDED (pfo->PerformOperations()); + } + } + + pfo->Release(); + } + + psi->Release(); + } + + return ret; +} + static int CALLBACK -SKIF_Util_FileExplorer_BrowseFolder_CallbackProc (HWND hWnd,UINT uMsg, LPARAM lParam, LPARAM lpData) +SKIF_Util_FileExplorer_BrowseForFolder_CallbackProc (HWND hWnd,UINT uMsg, LPARAM lParam, LPARAM lpData) { UNREFERENCED_PARAMETER (lParam); @@ -2715,7 +2748,7 @@ SKIF_Util_FileExplorer_BrowseFolder_CallbackProc (HWND hWnd,UINT uMsg, LPARAM lP } std::wstring -SKIF_Util_FileExplorer_BrowseFolder (PCWSTR defaultPath) +SKIF_Util_FileExplorer_BrowseForFolder (PCWSTR defaultPath) { TCHAR path[MAX_PATH]; @@ -2723,7 +2756,7 @@ SKIF_Util_FileExplorer_BrowseFolder (PCWSTR defaultPath) bi = { }; bi.lpszTitle = L"Select a new screenshot folder for SKIV to use:"; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; - bi.lpfn = SKIF_Util_FileExplorer_BrowseFolder_CallbackProc; + bi.lpfn = SKIF_Util_FileExplorer_BrowseForFolder_CallbackProc; bi.lParam = (LPARAM) defaultPath; LPITEMIDLIST pidl = SHBrowseForFolder ( &bi ); @@ -2747,6 +2780,8 @@ SKIF_Util_FileExplorer_BrowseFolder (PCWSTR defaultPath) return L""; } + + #if NTDDI_VERSION < NTDDI_WIN10_RS5 // Effective Power Mode (Windows 10 1809+) typedef enum EFFECTIVE_POWER_MODE { From 8488d0dcd2d5c83f487dcac930941e9d9f6740b5 Mon Sep 17 00:00:00 2001 From: Aemony Date: Sun, 22 Mar 2026 18:50:45 +0100 Subject: [PATCH 03/23] Improved sort handling (esp. with network shares) --- include/utility/image.h | 7 +++--- src/tabs/viewer.cpp | 28 ++++++++++++++++++----- src/utility/image.cpp | 50 +++++++++++++++++------------------------ 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/include/utility/image.h b/include/utility/image.h index f480d55..fe3726f 100644 --- a/include/utility/image.h +++ b/include/utility/image.h @@ -355,6 +355,7 @@ struct skiv_image_directory_s { std::vector fileList; std::vector::iterator activeFile; std::vector sortColumns; // File Explorer + bool fileDeleted = false; void reset (void); void setImage (const std::wstring& path); @@ -369,7 +370,7 @@ struct skiv_image_directory_s { void updateFolderData (void); // Win32 File Explorer based sorting - bool sortByColumns (void); - void sortByFilename (void); - bool updateSortColumns (void); // Returns true if the sort columns have changed + bool sortByColumns (void); + bool sortByFilename (void); + bool updateSortOrder (void); // Returns true when sorted }; \ No newline at end of file diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 6828636..753ef01 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -2931,6 +2931,9 @@ SKIF_UI_Tab_DrawViewer (void) // Identify when we're dealing with a whole new folder if (cover.file_info.folder_path != _current_folder.folder_path) { + //PLOG_DEBUG << "cover.file_info.folder_path: " << cover.file_info.folder_path; + //PLOG_DEBUG << "_current_folder.folder_path: " << _current_folder.folder_path; + _current_folder.reset(); //_current_folder.orig_path = cover.file_info.path; @@ -2948,11 +2951,16 @@ SKIF_UI_Tab_DrawViewer (void) if (! _current_folder.fileList.empty() && cover.file_info.filename != _current_folder.activeFile->filename) { - // Re-sort the folder if the sort columns have changed - if (_current_folder.updateSortColumns ( )) - _current_folder.sortByColumns ( ); + //PLOG_DEBUG << " cover.file_info.filename: " << cover.file_info.filename; + //PLOG_DEBUG << "_current_folder.activeFile->filename: " << _current_folder.activeFile->filename; + // Re-sort the folder if the sort columns have changed + _current_folder.updateSortOrder ( ); _current_folder.updateFileIterator (cover.file_info.path); + + // If the selected file was changed from within _current_folder + if (cover.file_info.filename != _current_folder.activeFile->filename) + dragDroppedFilePath = _current_folder.activeFile->path; } // Identify when the folder was changed outside of the app @@ -2964,8 +2972,18 @@ SKIF_UI_Tab_DrawViewer (void) if (dwLastSignaled != 0 && dwLastSignaled + 500 < SKIF_Util_timeGetTime()) { - _current_folder.updateFolderData ( ); - _current_folder.updateFileIterator (cover.file_info.path); + if (! _current_folder.fileDeleted) + { + _current_folder.updateFolderData ( ); + _current_folder.updateSortOrder ( ); + _current_folder.updateFileIterator (cover.file_info.path); + + // If the selected file was changed from within _current_folder + if (cover.file_info.filename != _current_folder.activeFile->filename) + dragDroppedFilePath = _current_folder.activeFile->path; + } else + _current_folder.fileDeleted = false; + dwLastSignaled = 0; } } diff --git a/src/utility/image.cpp b/src/utility/image.cpp index 165878e..6f4f2d8 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -4031,6 +4031,8 @@ skiv_image_directory_s::deleteImage (void) if (fileList.empty()) return L""; + fileDeleted = true; + activeFile = fileList.erase (activeFile); // Apparently erase() does not select the new populated end() ? Odd... @@ -4046,17 +4048,10 @@ skiv_image_directory_s::updateFileIterator (const std::wstring& path) { activeFile = std::find_if (fileList.begin(), fileList.end(), [&](const fd_s& file) { return file.path == path; }); - /* - // Set the index to the proper position - fileListIndex = 0; - for (auto& file : fileList) - { - if (filename != file.name) - fileListIndex++; - else - break; - } - */ + // If the file was removed from File Explorer, reset to first item + // TODO: Fix proper file tracking so we can detect removed files and just go to one of the nearby ones + if (activeFile->path.empty() && ! fileList.empty()) + activeFile = fileList.begin(); } // Retrieve all files in the folder, and identify our current place among them... @@ -4065,6 +4060,9 @@ skiv_image_directory_s::updateFolderData (void) { HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATA ffd = { }; + + auto old = fileList; + auto oldIt = std::find_if(old.begin(), old.end(), [&](const fd_s& file) { return file.path == activeFile->path; }); fileList.clear(); PLOG_DEBUG << "Discovering ... " << (folder_path + LR"(\*.*)"); @@ -4098,15 +4096,6 @@ skiv_image_directory_s::updateFolderData (void) filtered.push_back (file); fileList = filtered; - - if (! fileList.empty()) - { - temp_time = SKIF_Util_timeGetTime1(); - - // Let us try File Explorer sort first - if (updateSortColumns ( ) && sortByColumns ( )) { } - else sortByFilename ( ); - } } PLOG_DEBUG << "Found " << fileList.size() << " supported images in the folder."; @@ -4289,26 +4278,26 @@ GetFolderSortColumns (const std::wstring& path, std::vector& sortCol } bool -skiv_image_directory_s::updateSortColumns (void) +skiv_image_directory_s::updateSortOrder (void) { std::vector oldSort = sortColumns; if (! GetFolderSortColumns (folder_path, sortColumns)) - return true; + return sortByFilename ( ); if (sortColumns.size() != oldSort.size()) - return true; + return sortByColumns ( ); for (int i = 0; i < oldSort.size(); i++) { if ((oldSort[i].propkey != sortColumns[i].propkey) || (oldSort[i].direction != sortColumns[i].direction)) - return true; + return sortByColumns ( ); } - return false; + return sortByColumns ( ); } -void +bool skiv_image_directory_s::sortByFilename (void) { std::sort (fileList.begin(), @@ -4321,13 +4310,15 @@ skiv_image_directory_s::sortByFilename (void) ); PLOG_VERBOSE << "Sorted alphabetically!"; + + return true; } bool skiv_image_directory_s::sortByColumns (void) { if (sortColumns.empty()) - return false; + return sortByFilename ( ); struct cf_s { @@ -4351,7 +4342,7 @@ skiv_image_directory_s::sortByColumns (void) { _com_error err(hr); PLOG_ERROR << "Operation [CreateBindCtx] failed with error: " << SK_WideCharToUTF8 (err.ErrorMessage()); - return false; + return sortByFilename ( ); } for (const auto& file : fileList) @@ -4398,7 +4389,8 @@ skiv_image_directory_s::sortByColumns (void) temp_time = SKIF_Util_timeGetTime1(); - std::sort (cache.begin(), cache.end(), [&](const cf_s& a, const cf_s& b) + std::sort (cache.begin(), cache.end(), + [&](const cf_s& a, const cf_s& b) { for (size_t i = 0; i < sortColumns.size(); ++i) { From 63dffafc4f4351689aa8ee243d725e81c921be84 Mon Sep 17 00:00:00 2001 From: Aemony Date: Sun, 22 Mar 2026 19:48:29 +0100 Subject: [PATCH 04/23] Added native context menu --- include/utility/utility.h | 1 + src/tabs/viewer.cpp | 12 ++++++--- src/utility/utility.cpp | 53 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/include/utility/utility.h b/include/utility/utility.h index 4b4d81d..31506ea 100644 --- a/include/utility/utility.h +++ b/include/utility/utility.h @@ -226,6 +226,7 @@ DirectX::Image SKIF_Util_GetClipboardBitmapData (void); std::wstring SKIF_Util_AddEnvironmentBlock (const void* pEnvBlock, const std::wstring& varName, const std::wstring& varValue); void SKIF_Util_FileExplorer_SelectFile (PCWSTR filePath); bool SKIF_Util_FileExplorer_DeleteFile (PCWSTR filePath, bool hideWarning); +void SKIF_Util_FileExplorer_ContextMenuFile(PCWSTR filePath, HWND hWndOwner); std::wstring SKIF_Util_FileExplorer_BrowseForFolder(PCWSTR defaultPath); std::string SKIF_Util_GetWindowMessageAsStr (UINT msg); diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 753ef01..57eb565 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -4004,7 +4004,7 @@ SKIF_UI_Tab_DrawViewer (void) ImGui::Separator ( ); - if (SKIF_ImGui_MenuItemEx2 ("Copy", ICON_FA_CLIPBOARD, ImGui::GetStyleColorVec4(ImGuiCol_Text), "Ctrl+C")) + if (SKIF_ImGui_MenuItemEx2 ("Copy", ICON_FA_CLIPBOARD, ImGui::GetStyleColorVec4(ImGuiCol_Text), "Ctrl+C")) wantCopyToClipboard = true; if (SKIF_ImGui_MenuItemEx2 ("Close", 0, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_Info), "Ctrl+W")) _SwapOutCover (); @@ -4106,8 +4106,14 @@ SKIF_UI_Tab_DrawViewer (void) ImGui::Separator ( ); - if (! cover.file_info.path.empty() && SKIF_ImGui_MenuItemEx2 ("Browse Folder", ICON_FA_FOLDER_OPEN, ImColor(255, 207, 72), "Ctrl+E")) - SKIF_Util_FileExplorer_SelectFile (cover.file_info.path.c_str()); + if (! cover.file_info.path.empty()) + { + if (SKIF_ImGui_MenuItemEx2 ("Browse Folder", ICON_FA_FOLDER_OPEN, ImColor(255, 207, 72), "Ctrl+E")) + SKIF_Util_FileExplorer_SelectFile (cover.file_info.path.c_str()); + + if (SKIF_ImGui_MenuItemEx2 ("Context Menu", ICON_FA_WINDOWS, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_Info))) + SKIF_Util_FileExplorer_ContextMenuFile (cover.file_info.path.c_str(), SKIF_Notify_hWnd); + } } ImGui::Separator ( ); diff --git a/src/utility/utility.cpp b/src/utility/utility.cpp index 62c77a1..46a919b 100644 --- a/src/utility/utility.cpp +++ b/src/utility/utility.cpp @@ -2680,7 +2680,7 @@ SKIF_Util_FileExplorer_SelectFile (PCWSTR filePath) { } // Success // Use the task allocator to free to returned pidl - ILFree (iidlPtr); + CoTaskMemFree (iidlPtr); } } @@ -2735,6 +2735,57 @@ SKIF_Util_FileExplorer_DeleteFile (PCWSTR filePath, bool hideWarning) return ret; } +void +SKIF_Util_FileExplorer_ContextMenuFile (PCWSTR filePath, HWND hWndOwner) +{ + // You should call this function from a background thread. + // Failure to do so could cause the UI to stop responding. + PIDLIST_ABSOLUTE iidlPtr = nullptr; + HRESULT hr = SHParseDisplayName (filePath, NULL, &iidlPtr, 0, nullptr); + // Let us take the risk since TrackPopupMenuEx() needs to be called from the UI thread + + if (SUCCEEDED (hr)) + { + CComPtr parentFolder; + LPCITEMIDLIST child = nullptr; + + hr = SHBindToParent (iidlPtr, IID_PPV_ARGS(&parentFolder), &child); + + if (SUCCEEDED (hr)) + { + CComPtr ctxMenu; + hr = parentFolder->GetUIObjectOf (hWndOwner, 1, &child, IID_IContextMenu, nullptr, (void**)&ctxMenu); + + if (SUCCEEDED (hr)) + { + HMENU hMenu = CreatePopupMenu(); + ctxMenu->QueryContextMenu (hMenu, 0, 1, 0x7FFF, CMF_NORMAL); + + POINT cur = { }; + GetCursorPos (&cur); + + int cmd = TrackPopupMenuEx (hMenu, TPM_RETURNCMD, cur.x, cur.y, hWndOwner, nullptr); + + if (cmd > 0) + { + CMINVOKECOMMANDINFOEX + info = { sizeof(info) }; + info.fMask = CMIC_MASK_UNICODE; + info.hwnd = hWndOwner; + info.lpVerb = MAKEINTRESOURCEA(cmd - 1); + info.nShow = SW_SHOWNORMAL; + + ctxMenu->InvokeCommand ((LPCMINVOKECOMMANDINFO)&info); + } + + DestroyMenu (hMenu); + } + } + + CoTaskMemFree (iidlPtr); + } +} + static int CALLBACK SKIF_Util_FileExplorer_BrowseForFolder_CallbackProc (HWND hWnd,UINT uMsg, LPARAM lParam, LPARAM lpData) From 7b26d537e4c27a997a68c0b136e942cffa21055e Mon Sep 17 00:00:00 2001 From: Aemony Date: Sun, 22 Mar 2026 20:35:00 +0100 Subject: [PATCH 05/23] Improvements to reduce overhead on folder changes - Long-term the refreshes have to be moved to a worker thread --- src/tabs/viewer.cpp | 10 +++++----- src/utility/image.cpp | 38 +++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 57eb565..a70531b 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -2960,27 +2960,27 @@ SKIF_UI_Tab_DrawViewer (void) // If the selected file was changed from within _current_folder if (cover.file_info.filename != _current_folder.activeFile->filename) - dragDroppedFilePath = _current_folder.activeFile->path; + dragDroppedFilePath = _current_folder.activeFile->path; } // Identify when the folder was changed outside of the app + // TODO: Filter out various changes (e.g. Thumbs.db updates) if (_current_folder.watch.isSignaled (_current_folder.folder_path)) { dwLastSignaled = SKIF_Util_timeGetTime(); - PLOG_VERBOSE << "_current_folder.watch was signaled! Delay checking the folder for another 500ms..."; + PLOG_VERBOSE << "_current_folder.watch was signaled! Delay checking the folder for another 2500 ms..."; } - if (dwLastSignaled != 0 && dwLastSignaled + 500 < SKIF_Util_timeGetTime()) + if (dwLastSignaled != 0 && dwLastSignaled + 2500 < SKIF_Util_timeGetTime()) { if (! _current_folder.fileDeleted) { _current_folder.updateFolderData ( ); - _current_folder.updateSortOrder ( ); _current_folder.updateFileIterator (cover.file_info.path); // If the selected file was changed from within _current_folder if (cover.file_info.filename != _current_folder.activeFile->filename) - dragDroppedFilePath = _current_folder.activeFile->path; + dragDroppedFilePath = _current_folder.activeFile->path; } else _current_folder.fileDeleted = false; diff --git a/src/utility/image.cpp b/src/utility/image.cpp index 6f4f2d8..c6af83b 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -4055,29 +4055,28 @@ skiv_image_directory_s::updateFileIterator (const std::wstring& path) } // Retrieve all files in the folder, and identify our current place among them... +// TODO: Move this over unto a thread so as to not freeze the main UI thread void skiv_image_directory_s::updateFolderData (void) { HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATA ffd = { }; - auto old = fileList; - auto oldIt = std::find_if(old.begin(), old.end(), [&](const fd_s& file) { return file.path == activeFile->path; }); - fileList.clear(); + std::vector newList; PLOG_DEBUG << "Discovering ... " << (folder_path + LR"(\*.*)"); DWORD temp_time = SKIF_Util_timeGetTime1(); - hFind = + hFind = FindFirstFileExW ((folder_path + LR"(\*.*)").c_str(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH); if (INVALID_HANDLE_VALUE != hFind) { - fileList.push_back ({ ffd.cFileName, folder_path + LR"(\)" + ffd.cFileName, ffd }); + newList.push_back ({ ffd.cFileName, folder_path + LR"(\)" + ffd.cFileName, ffd }); while (FindNextFile (hFind, &ffd)) - fileList.push_back ({ ffd.cFileName, folder_path + LR"(\)" + ffd.cFileName, ffd }); + newList.push_back ({ ffd.cFileName, folder_path + LR"(\)" + ffd.cFileName, ffd }); FindClose (hFind); } @@ -4085,20 +4084,37 @@ skiv_image_directory_s::updateFolderData (void) PLOG_DEBUG << "Operation [FindFirstFileExW/FindNextFile] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; temp_time = SKIF_Util_timeGetTime1(); - if (! fileList.empty()) + if (! newList.empty()) { std::vector filtered; extern bool isExtensionSupported (const std::wstring extension); // Filter out unsupported file formats using their file extension - for (auto& file : fileList) + for (auto& file : newList) if (isExtensionSupported (std::filesystem::path(file.filename).extension().wstring())) filtered.push_back (file); - fileList = filtered; + newList = filtered; + } + + bool changed = (newList.size() != fileList.size()); + if (! changed) + { + // Both lists can be sorted differently (alphabetical vs. sort columns) so use find_if for each element + for (auto& item : newList) + { + if (std::find_if (fileList.begin(), fileList.end(), [&](const fd_s& file) { return file.path == item.path; }) == fileList.end()) + changed = true; + } } - PLOG_DEBUG << "Found " << fileList.size() << " supported images in the folder."; + if (changed) + { + fileList = std::move(newList); + PLOG_DEBUG << "Found " << fileList.size() << " supported images in the folder."; + // Update the sort order + updateSortOrder ( ); + } } @@ -4301,7 +4317,7 @@ bool skiv_image_directory_s::sortByFilename (void) { std::sort (fileList.begin(), - fileList.end (), + fileList.end (), []( const fd_s& a, const fd_s& b ) -> int { From 0b6a68185ae1a4c3fb1d6b8943ea944987acbf56 Mon Sep 17 00:00:00 2001 From: Aemony Date: Mon, 23 Mar 2026 18:30:19 +0100 Subject: [PATCH 06/23] Prune temporary files, leaving only the 10 latest --- include/utility/utility.h | 3 ++ src/SKIV.cpp | 47 +---------------- src/utility/image.cpp | 3 ++ src/utility/utility.cpp | 106 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 46 deletions(-) diff --git a/include/utility/utility.h b/include/utility/utility.h index 31506ea..2bb8bf1 100644 --- a/include/utility/utility.h +++ b/include/utility/utility.h @@ -149,6 +149,7 @@ bool SKIF_Util_HasFileSignature (const std::vector& bool SKIF_Util_HasFileExtension (const std::wstring extension, const FileSignature& signature); // Usernames + std:: string SKIF_Util_StripPersonalData (std:: string input); std::wstring SKIF_Util_StripPersonalData (std::wstring input); void SKIF_Util_Debug_LogUserNames (void); @@ -228,6 +229,8 @@ void SKIF_Util_FileExplorer_SelectFile (PCWSTR filePath); bool SKIF_Util_FileExplorer_DeleteFile (PCWSTR filePath, bool hideWarning); void SKIF_Util_FileExplorer_ContextMenuFile(PCWSTR filePath, HWND hWndOwner); std::wstring SKIF_Util_FileExplorer_BrowseForFolder(PCWSTR defaultPath); +bool SKIF_Util_Files_PruneOlderThan (std::wstring path, ULONGLONG secondsSince); +bool SKIF_Util_Files_PruneToLatestN (std::wstring path, int filesToRetain); std::string SKIF_Util_GetWindowMessageAsStr (UINT msg); diff --git a/src/SKIV.cpp b/src/SKIV.cpp index ea40793..440a59d 100644 --- a/src/SKIV.cpp +++ b/src/SKIV.cpp @@ -942,52 +942,7 @@ void SKIF_Initialize (LPWSTR lpCmdLine) std::filesystem::create_directories (_path_cache.skiv_temp, ec); // Clear out any temp files older than a day - auto _isDayOld = [&](FILETIME ftLastWriteTime) -> bool - { - FILETIME ftSystemTime{}, ftAdjustedFileTime{}; - SYSTEMTIME systemTime{}; - GetSystemTime (&systemTime); - - if (SystemTimeToFileTime(&systemTime, &ftSystemTime)) - { - ULARGE_INTEGER uintLastWriteTime{}; - - // Copy to ULARGE_INTEGER union to perform 64-bit arithmetic - uintLastWriteTime.HighPart = ftLastWriteTime.dwHighDateTime; - uintLastWriteTime.LowPart = ftLastWriteTime.dwLowDateTime; - - // Perform 64-bit arithmetic to add 1 day to last modified timestamp - uintLastWriteTime.QuadPart = uintLastWriteTime.QuadPart + ULONGLONG(1 * 24 * 60 * 60 * 1.0e+7); - - // Copy the results to an FILETIME struct - ftAdjustedFileTime.dwHighDateTime = uintLastWriteTime.HighPart; - ftAdjustedFileTime.dwLowDateTime = uintLastWriteTime.LowPart; - - // Compare with system time, and if system time is later (1), then return true - if (CompareFileTime (&ftSystemTime, &ftAdjustedFileTime) == 1) - return true; - } - - return false; - }; - - HANDLE hFind = INVALID_HANDLE_VALUE; - WIN32_FIND_DATA ffd = { }; - - hFind = - FindFirstFileExW ((tempDir + L"*").c_str(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, NULL); - - if (INVALID_HANDLE_VALUE != hFind) - { - if (_isDayOld (ffd.ftLastWriteTime)) - DeleteFile ((tempDir + ffd.cFileName).c_str()); - - while (FindNextFile (hFind, &ffd)) - if (_isDayOld (ffd.ftLastWriteTime)) - DeleteFile ((tempDir + ffd.cFileName).c_str()); - - FindClose (hFind); - } + SKIF_Util_Files_PruneOlderThan (tempDir, 24 * 60 * 60); // Populate SKIV's default screenshot folder const std::wstring screenshotsDir = diff --git a/src/utility/image.cpp b/src/utility/image.cpp index c6af83b..268ce76 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -1093,6 +1093,9 @@ using namespace DirectX; wsFilename += L"_"; + if (! isPersistent) + SKIF_Util_Files_PruneToLatestN (_path_cache.skiv_temp, 10); + // DateTime SYSTEMTIME st; GetLocalTime(&st); diff --git a/src/utility/utility.cpp b/src/utility/utility.cpp index 46a919b..a3148f5 100644 --- a/src/utility/utility.cpp +++ b/src/utility/utility.cpp @@ -2831,6 +2831,112 @@ SKIF_Util_FileExplorer_BrowseForFolder (PCWSTR defaultPath) return L""; } +bool +SKIF_Util_Files_PruneOlderThan (std::wstring path, ULONGLONG secondsSince) +{ + if (path.empty()) + return false; + + if (! path.ends_with (LR"(\)")) + path += LR"(\)"; + + // Clear out any temp files older than the threshold + auto _isLastModified = [&](FILETIME ftLastWriteTime) -> bool + { + FILETIME ftSystemTime{}, ftAdjustedFileTime{}; + SYSTEMTIME systemTime{}; + GetSystemTime (&systemTime); + + if (SystemTimeToFileTime (&systemTime, &ftSystemTime)) + { + ULARGE_INTEGER uintLastWriteTime{}; + + // Copy to ULARGE_INTEGER union to perform 64-bit arithmetic + uintLastWriteTime.HighPart = ftLastWriteTime.dwHighDateTime; + uintLastWriteTime.LowPart = ftLastWriteTime.dwLowDateTime; + + // Perform 64-bit arithmetic to add the required amount of seconds to last modified timestamp + uintLastWriteTime.QuadPart = uintLastWriteTime.QuadPart + ULONGLONG(1 * (secondsSince) * 1.0e+7); + + // Copy the results to an FILETIME struct + ftAdjustedFileTime.dwHighDateTime = uintLastWriteTime.HighPart; + ftAdjustedFileTime.dwLowDateTime = uintLastWriteTime.LowPart; + + // Compare with system time, and if system time is later (1), then return true + if (CompareFileTime (&ftSystemTime, &ftAdjustedFileTime) == 1) + return true; + } + + return false; + }; + + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA ffd = { }; + + hFind = + FindFirstFileExW ((path + L"*").c_str(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, NULL); + + if (INVALID_HANDLE_VALUE != hFind) + { + if (_isLastModified (ffd.ftLastWriteTime)) + DeleteFile ((path + ffd.cFileName).c_str()); + + while (FindNextFile (hFind, &ffd)) + if (_isLastModified (ffd.ftLastWriteTime)) + DeleteFile ((path + ffd.cFileName).c_str()); + + FindClose (hFind); + } else return false; + + return true; +} + +bool +SKIF_Util_Files_PruneToLatestN (std::wstring path, int filesToRetain) +{ + if (path.empty()) + return false; + + if (! path.ends_with (LR"(\)")) + path += LR"(\)"; + + HANDLE hFind = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA ffd = { }; + std::vector files; + + // This excludes the . and .. items + auto _isValid = [](const wchar_t* str) -> bool + { return (! ((str[0] == '.') && ((str[1] == '\0') || (str[1] == '.' && str[2] == '\0')))); }; + + hFind = + FindFirstFileExW ((path + L"*").c_str(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, NULL); + + if (INVALID_HANDLE_VALUE != hFind) + { + if (_isValid (ffd.cFileName)) + files.push_back (ffd); + + while (FindNextFile (hFind, &ffd)) + if (_isValid (ffd.cFileName)) + files.push_back (ffd); + + FindClose (hFind); + } else return false; + + if (files.size() > filesToRetain) + { + std::sort (files.begin(), files.end(), [](const WIN32_FIND_DATA& a, const WIN32_FIND_DATA& b) + { return (CompareFileTime (&a.ftLastWriteTime, &b.ftLastWriteTime) == -1); } // First file time is earlier than second file time. + ); + + for (int i = 0; i < (files.size() - filesToRetain); i++) + DeleteFile ((path + files[i].cFileName).c_str()); + + return true; + } + + return false; +} #if NTDDI_VERSION < NTDDI_WIN10_RS5 From 152938de948e3c6f4d4ebd847acc1d52c72353e0 Mon Sep 17 00:00:00 2001 From: Aemony Date: Mon, 23 Mar 2026 23:28:21 +0100 Subject: [PATCH 07/23] Offload folder scan/refresh to a background thread --- include/utility/image.h | 10 ++- src/tabs/viewer.cpp | 36 +++++--- src/utility/image.cpp | 185 ++++++++++++++++++++++++++++++---------- 3 files changed, 168 insertions(+), 63 deletions(-) diff --git a/include/utility/image.h b/include/utility/image.h index fe3726f..c7a0dcb 100644 --- a/include/utility/image.h +++ b/include/utility/image.h @@ -367,10 +367,12 @@ struct skiv_image_directory_s { void updateFileIterator (const std::wstring& path); // Retrieve all files in the folder, and identify our current place among them... - void updateFolderData (void); + bool workerThread (bool runThread); // Returns true once new data have been swapped in (used to update the iterator elsewhere) // Win32 File Explorer based sorting - bool sortByColumns (void); - bool sortByFilename (void); - bool updateSortOrder (void); // Returns true when sorted +private: + static void updateFolderData (std::vector& list, std::vector& sortColumns, const std::wstring& path); + static bool updateSortOrder (std::vector& list, std::vector& sortColumns, const std::wstring& path); // Returns true when sorted + static bool sortByColumns (std::vector& list, const std::vector& sortColumns); + static bool sortByFilename (std::vector& list); }; \ No newline at end of file diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index a70531b..01c3147 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -2955,12 +2955,14 @@ SKIF_UI_Tab_DrawViewer (void) //PLOG_DEBUG << "_current_folder.activeFile->filename: " << _current_folder.activeFile->filename; // Re-sort the folder if the sort columns have changed - _current_folder.updateSortOrder ( ); - _current_folder.updateFileIterator (cover.file_info.path); + //_current_folder.updateSortOrder ( ); + //_current_folder.updateFileIterator (cover.file_info.path); + _current_folder.workerThread (true); + // _current_folder.updateFileIterator (cover.file_info.path); // If the selected file was changed from within _current_folder - if (cover.file_info.filename != _current_folder.activeFile->filename) - dragDroppedFilePath = _current_folder.activeFile->path; + //if (cover.file_info.filename != _current_folder.activeFile->filename) + // dragDroppedFilePath = _current_folder.activeFile->path; } // Identify when the folder was changed outside of the app @@ -2974,18 +2976,28 @@ SKIF_UI_Tab_DrawViewer (void) if (dwLastSignaled != 0 && dwLastSignaled + 2500 < SKIF_Util_timeGetTime()) { if (! _current_folder.fileDeleted) - { - _current_folder.updateFolderData ( ); - _current_folder.updateFileIterator (cover.file_info.path); - - // If the selected file was changed from within _current_folder - if (cover.file_info.filename != _current_folder.activeFile->filename) - dragDroppedFilePath = _current_folder.activeFile->path; - } else + _current_folder.workerThread (true); + else _current_folder.fileDeleted = false; dwLastSignaled = 0; } + + if (_current_folder.workerThread (false)) + { + _current_folder.updateFileIterator (cover.file_info.path); + + // If the selected file was changed from within _current_folder + if (cover.file_info.filename != _current_folder.activeFile->filename) + dragDroppedFilePath = _current_folder.activeFile->path; + + ImGui::InsertNotification ( + { + ImGuiToastType::Info, + 5000, + "Background Scan", "Surrounding images have been refreshed!" + }); + } } // Only apply changes to the scaling method if we actually have an image loaded diff --git a/src/utility/image.cpp b/src/utility/image.cpp index 268ce76..0050632 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -4057,35 +4057,138 @@ skiv_image_directory_s::updateFileIterator (const std::wstring& path) activeFile = fileList.begin(); } +bool +skiv_image_directory_s::workerThread (bool runThread) +{ + struct worker_thread_s { + std::wstring _path; + std::vector _fileList; + std::vector _sortColumns; + HANDLE hWorker = NULL; + unsigned int sWorker = 0; + }; + + static worker_thread_s* pthread_data = nullptr; + static std::wstring pending; + + // Only run one worker at once + if (runThread || ! pending.empty()) + { + // Add new stuff to the pending cache. + pending = folder_path; + + if (pthread_data == nullptr) + { + pthread_data = new worker_thread_s; + + // Swap over the pending path, with copies of our existing data... + pthread_data->_path = pending; + pthread_data->_fileList = fileList; + pthread_data->_sortColumns = sortColumns; + + HANDLE hWorkerThread = (HANDLE) + _beginthreadex (nullptr, 0x0, [](void* _input) -> unsigned + { + SKIF_Util_SetThreadDescription (GetCurrentThread (), L"SKIV_FolderWorker"); + + // Is this combo really appropriate for this thread? + //SKIF_Util_SetThreadPowerThrottling (GetCurrentThread (), 1); // Enable EcoQoS for this thread + //SetThreadPriority (GetCurrentThread (), THREAD_MODE_BACKGROUND_BEGIN); + + PLOG_VERBOSE << "SKIV_FolderWorker thread started!"; + + DWORD start = SKIF_Util_timeGetTime1(); + + worker_thread_s* _data = static_cast(_input); + + updateFolderData (_data->_fileList, _data->_sortColumns, _data->_path); + + PLOG_VERBOSE << "Thread [SKIF_LibraryWorker] took " << (SKIF_Util_timeGetTime1() - start) << " ms to complete!"; + + PLOG_VERBOSE << "SKIF_LibraryWorker thread stopped!"; + + //SetThreadPriority (GetCurrentThread (), THREAD_MODE_BACKGROUND_END); + + return 0; + }, pthread_data, 0x0, nullptr); + + bool threadCreated = (hWorkerThread != NULL); + + if (threadCreated) + { + pthread_data->hWorker = hWorkerThread; + pthread_data->sWorker = 1; + } + else // Someting went wrong during thread creation, so free up the memory we allocated earlier + { + delete pthread_data; + pthread_data = nullptr; + } + } + } + + // Only check our work if processNewWork is unset + if (! runThread && pthread_data != nullptr && pthread_data->sWorker == 1 && WaitForSingleObject (pthread_data->hWorker, 0) == WAIT_OBJECT_0) + { + // Only swap in the data if it is fresh + if (pending == pthread_data->_path) + { + fileList = pthread_data->_fileList; + sortColumns = pthread_data->_sortColumns; + pending.clear(); + } + + CloseHandle (pthread_data->hWorker); + pthread_data->hWorker = NULL; + pthread_data->sWorker = 2; + pthread_data->_path.clear(); + pthread_data->_fileList.clear(); + + delete pthread_data; + pthread_data = nullptr; + + PLOG_VERBOSE << "Swapped in the new folder data!"; + + return true; + } + + return false; +} + // Retrieve all files in the folder, and identify our current place among them... // TODO: Move this over unto a thread so as to not freeze the main UI thread void -skiv_image_directory_s::updateFolderData (void) +skiv_image_directory_s::updateFolderData (std::vector& list, std::vector& sortColumns, const std::wstring& path) { HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATA ffd = { }; std::vector newList; - PLOG_DEBUG << "Discovering ... " << (folder_path + LR"(\*.*)"); + PLOG_DEBUG << "Discovering ... " << (path + LR"(\*.*)"); DWORD temp_time = SKIF_Util_timeGetTime1(); + // This excludes the . and .. items + auto _isValid = [](const wchar_t* str) -> bool + { return (! ((str[0] == '.') && ((str[1] == '\0') || (str[1] == '.' && str[2] == '\0')))); }; + hFind = - FindFirstFileExW ((folder_path + LR"(\*.*)").c_str(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH); + FindFirstFileExW ((path + LR"(\*.*)").c_str(), FindExInfoBasic, &ffd, FindExSearchNameMatch, NULL, FIND_FIRST_EX_LARGE_FETCH); if (INVALID_HANDLE_VALUE != hFind) { - newList.push_back ({ ffd.cFileName, folder_path + LR"(\)" + ffd.cFileName, ffd }); + if (_isValid (ffd.cFileName)) + newList.push_back ({ ffd.cFileName, path + LR"(\)" + ffd.cFileName, ffd }); while (FindNextFile (hFind, &ffd)) - newList.push_back ({ ffd.cFileName, folder_path + LR"(\)" + ffd.cFileName, ffd }); + if (_isValid (ffd.cFileName)) + newList.push_back ({ ffd.cFileName, path + LR"(\)" + ffd.cFileName, ffd }); FindClose (hFind); } - PLOG_DEBUG << "Operation [FindFirstFileExW/FindNextFile] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; - temp_time = SKIF_Util_timeGetTime1(); + PLOG_VERBOSE << "Operation [FindFirstFileExW/FindNextFile] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; if (! newList.empty()) { @@ -4099,25 +4202,11 @@ skiv_image_directory_s::updateFolderData (void) newList = filtered; } - - bool changed = (newList.size() != fileList.size()); - if (! changed) - { - // Both lists can be sorted differently (alphabetical vs. sort columns) so use find_if for each element - for (auto& item : newList) - { - if (std::find_if (fileList.begin(), fileList.end(), [&](const fd_s& file) { return file.path == item.path; }) == fileList.end()) - changed = true; - } - } - - if (changed) - { - fileList = std::move(newList); - PLOG_DEBUG << "Found " << fileList.size() << " supported images in the folder."; - // Update the sort order - updateSortOrder ( ); - } + + list = std::move(newList); + PLOG_DEBUG << "Found " << list.size() << " supported images in the folder."; + // Update the sort order + updateSortOrder (list, sortColumns, path); } @@ -4172,7 +4261,7 @@ ComparePropVariants (const PROPVARIANT& a, const PROPVARIANT& b) } static bool -GetFolderSortColumns (const std::wstring& path, std::vector& sortColumns) +GetFolderSortColumns (std::vector& sortColumns, const std::wstring& path) { sortColumns.clear(); @@ -4297,30 +4386,30 @@ GetFolderSortColumns (const std::wstring& path, std::vector& sortCol } bool -skiv_image_directory_s::updateSortOrder (void) +skiv_image_directory_s::updateSortOrder (std::vector& list, std::vector& sortColumns, const std::wstring& path) { std::vector oldSort = sortColumns; - if (! GetFolderSortColumns (folder_path, sortColumns)) - return sortByFilename ( ); + if (! GetFolderSortColumns (sortColumns, path)) + return sortByFilename (list); if (sortColumns.size() != oldSort.size()) - return sortByColumns ( ); + return sortByColumns (list, sortColumns); for (int i = 0; i < oldSort.size(); i++) { if ((oldSort[i].propkey != sortColumns[i].propkey) || (oldSort[i].direction != sortColumns[i].direction)) - return sortByColumns ( ); + return sortByColumns (list, sortColumns); } - return sortByColumns ( ); + return sortByColumns (list, sortColumns); } bool -skiv_image_directory_s::sortByFilename (void) +skiv_image_directory_s::sortByFilename (std::vector& list) { - std::sort (fileList.begin(), - fileList.end (), + std::sort (list.begin(), + list.end (), []( const fd_s& a, const fd_s& b ) -> int { @@ -4334,10 +4423,10 @@ skiv_image_directory_s::sortByFilename (void) } bool -skiv_image_directory_s::sortByColumns (void) +skiv_image_directory_s::sortByColumns (std::vector& list, const std::vector& sortColumns) { if (sortColumns.empty()) - return sortByFilename ( ); + return sortByFilename (list); struct cf_s { @@ -4361,10 +4450,10 @@ skiv_image_directory_s::sortByColumns (void) { _com_error err(hr); PLOG_ERROR << "Operation [CreateBindCtx] failed with error: " << SK_WideCharToUTF8 (err.ErrorMessage()); - return sortByFilename ( ); + return sortByFilename (list); } - for (const auto& file : fileList) + for (const auto& file : list) { cf_s item; item.file = file; @@ -4381,6 +4470,8 @@ skiv_image_directory_s::sortByColumns (void) PLOG_ERROR << "Operation [RegisterObjectParam] failed with error: " << SK_WideCharToUTF8 (err.ErrorMessage()); } + // Using GPS_FASTPROPERTIESONLY speeds up the performance here a lot... but it also means that all sort methods will not be supported. + // For example, "System.ItemDate" (sort by Date) will not work and will instead mirror "System.ItemModified" (Date Modified) hr = SHGetPropertyStoreFromParsingName (file.path.c_str(), bindCtx, GPS_FASTPROPERTIESONLY | GPS_BESTEFFORT | GPS_NO_OPLOCK, IID_PPV_ARGS(&spStore)); sum_time_parsing += (SKIF_Util_timeGetTime1() - tmp); @@ -4403,8 +4494,8 @@ skiv_image_directory_s::sortByColumns (void) cache.push_back (std::move(item)); } - PLOG_DEBUG << "Operation [SHGetPropertyStoreFromParsingName] took " << sum_time_parsing << " ms."; - PLOG_DEBUG << "Operation [IPropertyStore::GetValue] took " << sum_time_getvalue << " ms."; + PLOG_VERBOSE << "Operation [SHGetPropertyStoreFromParsingName] took " << sum_time_parsing << " ms."; + PLOG_VERBOSE << "Operation [IPropertyStore::GetValue] took " << sum_time_getvalue << " ms."; temp_time = SKIF_Util_timeGetTime1(); @@ -4427,21 +4518,21 @@ skiv_image_directory_s::sortByColumns (void) } ); - PLOG_DEBUG << "Operation [SortItems] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; + PLOG_VERBOSE << "Operation [SortItems] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; temp_time = SKIF_Util_timeGetTime1(); - fileList.clear(); + list.clear(); for (auto& item : cache) { - fileList.push_back (item.file); + list.push_back (item.file); for (auto& v : item.values) PropVariantClear (&v); } - PLOG_DEBUG << "Operation [Cleanup] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; - PLOG_DEBUG << "Operation took ~" << (sum_time_parsing + sum_time_getvalue) << " ms."; + PLOG_VERBOSE << "Operation [Cleanup] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; + PLOG_VERBOSE << "Operation took ~" << (sum_time_parsing + sum_time_getvalue) << " ms."; PLOG_VERBOSE << "Sorted based on File Explorer columns!"; From 9251ac35071632649a03e9d1e3c5d92d32b1dbd7 Mon Sep 17 00:00:00 2001 From: Aemony Date: Tue, 24 Mar 2026 18:06:56 +0100 Subject: [PATCH 08/23] Optimized background folder refresh a bit --- include/utility/image.h | 10 ++-- src/tabs/viewer.cpp | 23 +++++---- src/utility/image.cpp | 107 +++++++++++++++++++++------------------- 3 files changed, 72 insertions(+), 68 deletions(-) diff --git a/include/utility/image.h b/include/utility/image.h index c7a0dcb..76881a0 100644 --- a/include/utility/image.h +++ b/include/utility/image.h @@ -367,12 +367,12 @@ struct skiv_image_directory_s { void updateFileIterator (const std::wstring& path); // Retrieve all files in the folder, and identify our current place among them... - bool workerThread (bool runThread); // Returns true once new data have been swapped in (used to update the iterator elsewhere) + int workerThread (bool runThread); // 0 = Not done, 1 = No change in sort order (i.e. same files as before) , 2 = Change in the sort order (i.e. new files/folder) // Win32 File Explorer based sorting private: - static void updateFolderData (std::vector& list, std::vector& sortColumns, const std::wstring& path); - static bool updateSortOrder (std::vector& list, std::vector& sortColumns, const std::wstring& path); // Returns true when sorted - static bool sortByColumns (std::vector& list, const std::vector& sortColumns); - static bool sortByFilename (std::vector& list); + static bool updateFolderData (std::vector& list, std::vector& sortColumns, const std::wstring& path); + static void updateSortOrder (std::vector& list, std::vector& sortColumns, const std::wstring& path); + static void sortByColumns (std::vector& list, const std::vector& sortColumns); + static void sortByFilename (std::vector& list); }; \ No newline at end of file diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 01c3147..3a77482 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -2966,14 +2966,11 @@ SKIF_UI_Tab_DrawViewer (void) } // Identify when the folder was changed outside of the app - // TODO: Filter out various changes (e.g. Thumbs.db updates) + //PLOG_VERBOSE << "_current_folder.watch was signaled! Delay checking the folder for another 5000 ms..."; if (_current_folder.watch.isSignaled (_current_folder.folder_path)) - { dwLastSignaled = SKIF_Util_timeGetTime(); - PLOG_VERBOSE << "_current_folder.watch was signaled! Delay checking the folder for another 2500 ms..."; - } - if (dwLastSignaled != 0 && dwLastSignaled + 2500 < SKIF_Util_timeGetTime()) + if (dwLastSignaled != 0 && dwLastSignaled + 5000 < SKIF_Util_timeGetTime()) { if (! _current_folder.fileDeleted) _current_folder.workerThread (true); @@ -2983,7 +2980,8 @@ SKIF_UI_Tab_DrawViewer (void) dwLastSignaled = 0; } - if (_current_folder.workerThread (false)) + int results = _current_folder.workerThread (false); + if (results > 0) { _current_folder.updateFileIterator (cover.file_info.path); @@ -2991,12 +2989,13 @@ SKIF_UI_Tab_DrawViewer (void) if (cover.file_info.filename != _current_folder.activeFile->filename) dragDroppedFilePath = _current_folder.activeFile->path; - ImGui::InsertNotification ( - { - ImGuiToastType::Info, - 5000, - "Background Scan", "Surrounding images have been refreshed!" - }); + if (results == 2) + ImGui::InsertNotification ( + { + ImGuiToastType::Info, + 5000, + "Background Scan", "Surrounding images have been refreshed!" + }); } } diff --git a/src/utility/image.cpp b/src/utility/image.cpp index 0050632..d26acc0 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -4057,10 +4057,11 @@ skiv_image_directory_s::updateFileIterator (const std::wstring& path) activeFile = fileList.begin(); } -bool +int skiv_image_directory_s::workerThread (bool runThread) { struct worker_thread_s { + bool _changed = false; std::wstring _path; std::vector _fileList; std::vector _sortColumns; @@ -4101,7 +4102,7 @@ skiv_image_directory_s::workerThread (bool runThread) worker_thread_s* _data = static_cast(_input); - updateFolderData (_data->_fileList, _data->_sortColumns, _data->_path); + _data->_changed = updateFolderData (_data->_fileList, _data->_sortColumns, _data->_path); PLOG_VERBOSE << "Thread [SKIF_LibraryWorker] took " << (SKIF_Util_timeGetTime1() - start) << " ms to complete!"; @@ -4130,11 +4131,20 @@ skiv_image_directory_s::workerThread (bool runThread) // Only check our work if processNewWork is unset if (! runThread && pthread_data != nullptr && pthread_data->sWorker == 1 && WaitForSingleObject (pthread_data->hWorker, 0) == WAIT_OBJECT_0) { + int state = 1; + // Only swap in the data if it is fresh if (pending == pthread_data->_path) { - fileList = pthread_data->_fileList; - sortColumns = pthread_data->_sortColumns; + if (pthread_data->_changed) + { + state = 2; + fileList = pthread_data->_fileList; + sortColumns = pthread_data->_sortColumns; + + PLOG_VERBOSE << "Swapped in the new folder data!"; + } + pending.clear(); } @@ -4147,23 +4157,22 @@ skiv_image_directory_s::workerThread (bool runThread) delete pthread_data; pthread_data = nullptr; - PLOG_VERBOSE << "Swapped in the new folder data!"; - - return true; + return state; } - return false; + return 0; } // Retrieve all files in the folder, and identify our current place among them... -// TODO: Move this over unto a thread so as to not freeze the main UI thread -void +bool skiv_image_directory_s::updateFolderData (std::vector& list, std::vector& sortColumns, const std::wstring& path) { - HANDLE hFind = INVALID_HANDLE_VALUE; + HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATA ffd = { }; - + std::vector newList; + std::vector oldList = list; + list.clear(); PLOG_DEBUG << "Discovering ... " << (path + LR"(\*.*)"); @@ -4192,21 +4201,40 @@ skiv_image_directory_s::updateFolderData (std::vector& list, std::vector filtered; extern bool isExtensionSupported (const std::wstring extension); // Filter out unsupported file formats using their file extension for (auto& file : newList) if (isExtensionSupported (std::filesystem::path(file.filename).extension().wstring())) - filtered.push_back (file); + list.push_back (file); + } - newList = filtered; + bool changed = (list.size() != oldList.size()); + if (! changed) + { + // Normalize sort order (A-Z) + sortByFilename (oldList); + sortByFilename (list); + + // Have an item changed? + for (int i = 0; i < list.size(); i++) + { + if (list[i].path != oldList[i].path) + { + changed = true; + break; + } + } } - - list = std::move(newList); - PLOG_DEBUG << "Found " << list.size() << " supported images in the folder."; - // Update the sort order - updateSortOrder (list, sortColumns, path); + + // Update sort order (SortColumns) + if (changed) + { + PLOG_DEBUG << "Found " << list.size() << " supported images in the folder."; + updateSortOrder (list, sortColumns, path); + } + + return changed; } @@ -4340,8 +4368,6 @@ GetFolderSortColumns (std::vector& sortColumns, const std::wstring& if (SUCCEEDED (spFV2->GetSortColumns (item.sortColumns.data(), sortColumnCount))) { - PLOG_VERBOSE << "Sort column count: " << sortColumnCount; - for (int col = 0; col < sortColumnCount; ++col) { PROPERTYKEY key = item.sortColumns[col].propkey; @@ -4365,9 +4391,10 @@ GetFolderSortColumns (std::vector& sortColumns, const std::wstring& if (it == candidates.end()) continue; - PLOG_VERBOSE << "Found sort columns!"; sortColumns = it->sortColumns; + //PLOG_VERBOSE << "Found sort columns!"; + /* for (int col = 0; col < sortColumns.size(); ++col) { @@ -4385,27 +4412,15 @@ GetFolderSortColumns (std::vector& sortColumns, const std::wstring& return false; } -bool +void skiv_image_directory_s::updateSortOrder (std::vector& list, std::vector& sortColumns, const std::wstring& path) { - std::vector oldSort = sortColumns; - if (! GetFolderSortColumns (sortColumns, path)) - return sortByFilename (list); - - if (sortColumns.size() != oldSort.size()) - return sortByColumns (list, sortColumns); - - for (int i = 0; i < oldSort.size(); i++) - { - if ((oldSort[i].propkey != sortColumns[i].propkey) || - (oldSort[i].direction != sortColumns[i].direction)) - return sortByColumns (list, sortColumns); - } - - return sortByColumns (list, sortColumns); + if (GetFolderSortColumns (sortColumns, path)) + sortByColumns (list, sortColumns); + else sortByFilename (list); } -bool +void skiv_image_directory_s::sortByFilename (std::vector& list) { std::sort (list.begin(), @@ -4416,13 +4431,9 @@ skiv_image_directory_s::sortByFilename (std::vector& list) return StrCmpLogicalW (a.filename.c_str(), b.filename.c_str()) < 0; } ); - - PLOG_VERBOSE << "Sorted alphabetically!"; - - return true; } -bool +void skiv_image_directory_s::sortByColumns (std::vector& list, const std::vector& sortColumns) { if (sortColumns.empty()) @@ -4519,7 +4530,6 @@ skiv_image_directory_s::sortByColumns (std::vector& list, const std::vecto ); PLOG_VERBOSE << "Operation [SortItems] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; - temp_time = SKIF_Util_timeGetTime1(); list.clear(); @@ -4531,10 +4541,5 @@ skiv_image_directory_s::sortByColumns (std::vector& list, const std::vecto PropVariantClear (&v); } - PLOG_VERBOSE << "Operation [Cleanup] took " << (SKIF_Util_timeGetTime1() - temp_time) << " ms."; PLOG_VERBOSE << "Operation took ~" << (sum_time_parsing + sum_time_getvalue) << " ms."; - - PLOG_VERBOSE << "Sorted based on File Explorer columns!"; - - return true; } \ No newline at end of file From 65834d37c8d6fb5f66f37e9ad55b58f136f96461 Mon Sep 17 00:00:00 2001 From: Aemony Date: Tue, 24 Mar 2026 18:35:14 +0100 Subject: [PATCH 09/23] Added toggles for hotkeys w/o unbinding keys --- include/utility/registry.h | 7 ++++++- src/SKIV.cpp | 9 ++++++--- src/tabs/settings.cpp | 39 ++++++++++++++++++++++++++++++-------- src/utility/registry.cpp | 3 +++ src/utility/sk_utility.cpp | 4 ++-- 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/include/utility/registry.h b/include/utility/registry.h index c5ee592..b6d8cb5 100644 --- a/include/utility/registry.h +++ b/include/utility/registry.h @@ -347,6 +347,10 @@ struct SKIF_RegistrySettings { SKIF_MakeRegKeyI ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\)", LR"(Screenshots Autosave)" ); + KeyValue regKVScreenshotsHotkeys = + SKIF_MakeRegKeyI ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\)", + LR"(Screenshots Hotkeys)" ); + // Wide Strings KeyValue regKVIgnoreUpdate = @@ -431,7 +435,8 @@ struct SKIF_RegistrySettings { int iHDRToneMapType = 8; // 0 = Do Nothing, 1 = Clip Luminance, 8 = Map to Display int iUIMode = 1; // 0 = Safe Mode (BitBlt), 1 = Normal, 2 = VRR Compatibility int iDiagnostics = 1; // 0 = None, 1 = Normal, 2 = Enhanced (not actually used yet) - CaptureMode eScreenshotsAutosave = CaptureMode_ALL; // Default to saving all types of screen captures + CaptureMode eScreenshotsAutosave = CaptureMode_ALL, // Default to saving all types of screen captures + eScreenshotsHotkeys = CaptureMode_ALL; // Default to enabling all hotkeys // Default settings (booleans) bool bAdjustWindow = false; // Adjust window size based on the image size? diff --git a/src/SKIV.cpp b/src/SKIV.cpp index 440a59d..e224d70 100644 --- a/src/SKIV.cpp +++ b/src/SKIV.cpp @@ -1393,9 +1393,12 @@ wWinMain ( _In_ HINSTANCE hInstance, SKIF_Util_RegisterHotKeyHDRToggle (_registry.kbToggleHDRDisplay.getKeybind()); // Register snipping hotkey - SKIF_Util_RegisterHotKeyCapture (CaptureMode_Window, _registry.kbCaptureWindow.getKeybind()); - SKIF_Util_RegisterHotKeyCapture (CaptureMode_Region, _registry.kbCaptureRegion.getKeybind()); - SKIF_Util_RegisterHotKeyCapture (CaptureMode_Screen, _registry.kbCaptureScreen.getKeybind()); + if ((_registry.eScreenshotsHotkeys & CaptureMode_Window) == CaptureMode_Window) + SKIF_Util_RegisterHotKeyCapture (CaptureMode_Window, _registry.kbCaptureWindow.getKeybind()); + if ((_registry.eScreenshotsHotkeys & CaptureMode_Region) == CaptureMode_Region) + SKIF_Util_RegisterHotKeyCapture (CaptureMode_Region, _registry.kbCaptureRegion.getKeybind()); + if ((_registry.eScreenshotsHotkeys & CaptureMode_Screen) == CaptureMode_Screen) + SKIF_Util_RegisterHotKeyCapture (CaptureMode_Screen, _registry.kbCaptureScreen.getKeybind()); // Register the HTML Format for the clipboard CF_HTML = RegisterClipboardFormatW (L"HTML Format"); diff --git a/src/tabs/settings.cpp b/src/tabs/settings.cpp index 847261b..8fb0f12 100644 --- a/src/tabs/settings.cpp +++ b/src/tabs/settings.cpp @@ -32,8 +32,10 @@ struct m_s { CaptureMode _type; char* _label; - char* _unique_id; - bool _value; + char* _main_id; + char* _disk_id; + bool _main_value; + bool _disk_value; kb_kv_s* _keybind; }; @@ -127,9 +129,9 @@ SKIF_UI_Tab_DrawSettings (void) static std::vector modes = { - { CaptureMode_Window, "Window", "###ModeToggle-Window", ((_registry.eScreenshotsAutosave & CaptureMode_Window) == CaptureMode_Window), &kbCaptureWindow }, - { CaptureMode_Region, "Region", "###ModeToggle-Region", ((_registry.eScreenshotsAutosave & CaptureMode_Region) == CaptureMode_Region), &kbCaptureRegion }, - { CaptureMode_Screen, "Screen", "###ModeToggle-Screen", ((_registry.eScreenshotsAutosave & CaptureMode_Screen) == CaptureMode_Screen), &kbCaptureScreen } + { CaptureMode_Window, "Window", "###ModeToggle-Window", "###DiskToggle-Window", ((_registry.eScreenshotsHotkeys & CaptureMode_Window) == CaptureMode_Window), ((_registry.eScreenshotsAutosave & CaptureMode_Window) == CaptureMode_Window), &kbCaptureWindow }, + { CaptureMode_Region, "Region", "###ModeToggle-Region", "###DiskToggle-Region", ((_registry.eScreenshotsHotkeys & CaptureMode_Region) == CaptureMode_Region), ((_registry.eScreenshotsAutosave & CaptureMode_Region) == CaptureMode_Region), &kbCaptureRegion }, + { CaptureMode_Screen, "Screen", "###ModeToggle-Screen", "###DiskToggle-Screen", ((_registry.eScreenshotsHotkeys & CaptureMode_Screen) == CaptureMode_Screen), ((_registry.eScreenshotsAutosave & CaptureMode_Screen) == CaptureMode_Screen), &kbCaptureScreen } }; ImGui::TextColored ( @@ -159,7 +161,25 @@ SKIF_UI_Tab_DrawSettings (void) ImGui::BeginGroup (); for (auto& mode : modes) { - ImGui::ItemSize (ImVec2 (0.0f, ImGui::GetFrameHeight ()), ImGui::GetStyle().FramePadding.y); + //ImGui::ItemSize (ImVec2 (0.0f, ImGui::GetFrameHeight ()), ImGui::GetStyle().FramePadding.y); + if (ImGui::Checkbox (mode._main_id, &mode._main_value)) + { + if (mode._main_value) + { + _registry.eScreenshotsHotkeys |= mode._type; + mode._keybind->_callback (mode._keybind->_key); + } + else { + _registry.eScreenshotsHotkeys &= ~mode._type; + SKIF_Util_UnregisterHotKeyCapture(mode._type); + } + + _registry.regKVScreenshotsHotkeys.putData (_registry.eScreenshotsHotkeys); + } + + if (! mode._main_value) + SKIF_ImGui_PushDisableState(); + ImGui::SameLine (); ImGui::Text ( "%s", mode._label); @@ -167,9 +187,9 @@ SKIF_UI_Tab_DrawSettings (void) ImGui::SameLine (); ImGui::SetCursorPosX (col2); - if (ImGui::Checkbox (mode._unique_id, &mode._value)) + if (ImGui::Checkbox (mode._disk_id, &mode._disk_value)) { - if (mode._value) + if (mode._disk_value) _registry.eScreenshotsAutosave |= mode._type; else _registry.eScreenshotsAutosave &= ~mode._type; @@ -188,6 +208,9 @@ SKIF_UI_Tab_DrawSettings (void) mode._keybind->_callback (mode._keybind->_key); } + + if (! mode._main_value) + SKIF_ImGui_PopDisableState(); } ImGui::EndGroup (); diff --git a/src/utility/registry.cpp b/src/utility/registry.cpp index 52866cf..407a597 100644 --- a/src/utility/registry.cpp +++ b/src/utility/registry.cpp @@ -372,6 +372,9 @@ SKIF_RegistrySettings::SKIF_RegistrySettings (void) if (regKVScreenshotsAutosave.hasData(&hKey)) eScreenshotsAutosave = regKVScreenshotsAutosave .getData (&hKey); + if (regKVScreenshotsHotkeys.hasData(&hKey)) + eScreenshotsHotkeys = regKVScreenshotsHotkeys .getData (&hKey); + if (regKVIgnoreUpdate.hasData(&hKey)) wsIgnoreUpdate = regKVIgnoreUpdate .getData (&hKey); diff --git a/src/utility/sk_utility.cpp b/src/utility/sk_utility.cpp index fe60511..094cece 100644 --- a/src/utility/sk_utility.cpp +++ b/src/utility/sk_utility.cpp @@ -756,14 +756,14 @@ SK_ImGui_KeybindSelect (SK_Keybind* keybind) if (! keybind) return false; - ImGui::PushStyleColor (ImGuiCol_Text, ImGui::GetStyleColorVec4 (ImGuiCol_SKIF_TextBase)); //ImVec4 (0.667f, 0.667f, 0.667f, 1.0f)); + //ImGui::PushStyleColor (ImGuiCol_Text, ImGui::GetStyleColorVec4 (ImGuiCol_SKIF_TextBase)); //ImVec4 (0.667f, 0.667f, 0.667f, 1.0f)); ImGui::PushItemWidth (ImGui::GetContentRegionAvail ().x); bool ret = ImGui::Selectable (keybind->human_readable_utf8.c_str(), false); ImGui::PopItemWidth (); - ImGui::PopStyleColor (); + //ImGui::PopStyleColor (); return ret; } From 085f99d252cfc908d0f48032b7f0e354387d6c9d Mon Sep 17 00:00:00 2001 From: Aemony Date: Tue, 24 Mar 2026 18:47:26 +0100 Subject: [PATCH 10/23] Added context menu to Settings tab --- src/tabs/settings.cpp | 92 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/tabs/settings.cpp b/src/tabs/settings.cpp index 8fb0f12..4204b64 100644 --- a/src/tabs/settings.cpp +++ b/src/tabs/settings.cpp @@ -39,6 +39,8 @@ struct m_s kb_kv_s* _keybind; }; +PopupState ContextMenuSettings = PopupState_Closed; + void SKIF_UI_Tab_DrawSettings (void) { @@ -1227,4 +1229,94 @@ SKIF_UI_Tab_DrawSettings (void) #pragma endregion +#pragma region ContextMenuSettings + + auto _IsRightClicked = [&](void) -> bool + { + if (ImGui::IsMouseClicked (ImGuiMouseButton_Right)) + { + return true; + } + + // Activate button held for >= .4 seconds -> right-click + if (ImGui::GetKeyData (ImGuiKey_GamepadFaceDown)->DownDuration > 0.4f && + ImGui::GetKeyData (ImGuiKey_GamepadFaceDown)->DownDuration < 5.0f) + { + ImGui::GetKeyData (ImGuiKey_GamepadFaceDown)->DownDuration = 5.0f; + ImGui::GetKeyData (ImGuiKey_GamepadFaceDown)->DownDurationPrev = 0.0f; + + ImGui::ClearActiveID ( ); + + return true; + } + + // Start button = Menu + if (ImGui::IsKeyPressed (ImGuiKey_GamepadStart)) + { + ImGui::GetKeyData (ImGuiKey_GamepadStart)->DownDuration = 0.01f; + ImGui::GetKeyData (ImGuiKey_GamepadStart)->DownDurationPrev = 0.00f; + + ImGui::ClearActiveID ( ); + + return true; + } + + return false; + }; + + // Act on all right clicks, because why not? :D + if (! SKIF_ImGui_IsAnyPopupOpen ( ) && _IsRightClicked ()) + ContextMenuSettings = PopupState_Open; + + // Open the Empty Space Menu + if (ContextMenuSettings == PopupState_Open) + ImGui::OpenPopup ("ContextMenuSettings"); + + + if (ImGui::BeginPopup ("ContextMenuSettings", ImGuiWindowFlags_NoMove)) + { + ContextMenuSettings = PopupState_Opened; + + ImGui::PushStyleColor (ImGuiCol_NavHighlight, ImVec4(0,0,0,0)); + + if (SKIF_ImGui_MenuItemEx2 ("Go back###GoBackCM", ICON_FA_LEFT_LONG)) // ICON_FA_LIST_CHECK + SKIF_Tab_ChangeTo = UITab_Viewer; + + ImGui::Separator ( ); + + if (SKIF_ImGui_MenuItemEx2 ("Fullscreen", SKIF_ImGui_IsFullscreen (SKIF_ImGui_hWnd) ? ICON_FA_DOWN_LEFT_AND_UP_RIGHT_TO_CENTER : ICON_FA_UP_RIGHT_AND_DOWN_LEFT_FROM_CENTER, ImGui::GetStyleColorVec4 (ImGuiCol_Text), "Ctrl+F")) + { + SKIF_ImGui_SetFullscreen (SKIF_ImGui_hWnd, ! SKIF_ImGui_IsFullscreen (SKIF_ImGui_hWnd)); + } + + ImGui::Separator ( ); + + if (_registry.bCloseToTray) + { + if (SKIF_ImGui_MenuItemEx2 ("Close app", 0, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_Info), "Esc")) + PostMessage (SKIF_Notify_hWnd, WM_SKIF_MINIMIZE, 0x0, 0x0); + } + + else + { + if (SKIF_ImGui_MenuItemEx2 ("Minimize", 0, ImGui::GetStyleColorVec4(ImGuiCol_SKIF_Info), "Ctrl+N")) + PostMessage (SKIF_Notify_hWnd, WM_SKIF_MINIMIZE, 0x0, 0x0); + } + + if (SKIF_ImGui_MenuItemEx2 ("Exit", 0, ImGui::GetStyleColorVec4 (ImGuiCol_SKIF_Info), "Ctrl+Q")) + { + extern bool bKeepWindowAlive; + bKeepWindowAlive = false; + } + + + ImGui::PopStyleColor ( ); + ImGui::EndPopup ( ); + } + + else + ContextMenuSettings = PopupState_Closed; + +#pragma endregion + } From e81bf4eab709a8fa7d931d6694294e0609ee458c Mon Sep 17 00:00:00 2001 From: Aemony Date: Tue, 24 Mar 2026 22:20:28 +0100 Subject: [PATCH 11/23] Added save to disk toggle to the region toolbar --- include/utility/registry.h | 1 + src/SKIV.cpp | 116 +++++++++++++++++++++++++++++-------- 2 files changed, 92 insertions(+), 25 deletions(-) diff --git a/include/utility/registry.h b/include/utility/registry.h index b6d8cb5..beadb54 100644 --- a/include/utility/registry.h +++ b/include/utility/registry.h @@ -516,6 +516,7 @@ struct SKIF_RegistrySettings { bool _RendererHDREnabled = false; // HDR Enabled bool _TouchDevice = false; bool _SnippingMode = false; + bool _SnippingModeInit = true; bool _SnippingModeExit = false; bool _SnippingModeTempHDR = false; int _SnippingTonemapsHDR = 2; diff --git a/src/SKIV.cpp b/src/SKIV.cpp index e224d70..13a0be0 100644 --- a/src/SKIV.cpp +++ b/src/SKIV.cpp @@ -2024,6 +2024,7 @@ wWinMain ( _In_ HINSTANCE hInstance, { _registry._SnippingMode = false; _registry._SnippingModeExit = false; + _registry._SnippingModeInit = true; extern HWND hwndBeforeSnip; extern HWND hwndTopBeforeSnip; @@ -2309,9 +2310,16 @@ wWinMain ( _In_ HINSTANCE hInstance, } }; - static bool clicked = false; + static bool clicked = false; + static bool _saveToDisk = false; + if (_registry._SnippingModeInit) + { + _registry._SnippingModeInit = false; + _saveToDisk = (_registry.eScreenshotsAutosave & CaptureMode_Region); + } - if (HDR_Image && SKIV_HDR) + static bool toolbar = true; // HDR_Image && SKIV_HDR + if (toolbar) { static ImVec2 vSnippingToolbarSize = ImVec2 (128.0f, 32.0f); @@ -2340,38 +2348,96 @@ wWinMain ( _In_ HINSTANCE hInstance, ImGui::TextUnformatted (ICON_FA_SCISSORS " Snipping Tool"); ImGui::Separator (); ImGui::PopStyleColor (); - ImGui::TreePush (""); - ImGui::RadioButton ("Keep HDR", &_registry._SnippingTonemapsHDR, 0); + + ImGui::Spacing (); + + // Save To Disk + ImGui::PushStyleColor (ImGuiCol_Text, ImGui::GetStyleColorVec4 (ImGuiCol_SKIF_Info)); + ImGui::SameLine (); + ImGui::Checkbox (" " ICON_FA_FLOPPY_DISK "###ToolbarSaveToDisk", &_saveToDisk); + ImGui::PopStyleColor (); if (ImGui::IsItemHovered ()) { ImGui::BeginTooltip (); - ImGui::TextUnformatted ("HDR PNG may have Compatibility Issues"); + ImGui::TextUnformatted ("Save captured screenshot?"); ImGui::Separator (); - ImGui::BulletText ("Generally the clipboard contents can only be pasted into browser-derived software (i.e. built using Chromium, Electron) and SKIV."); - ImGui::BulletText ("Some browsers cannot interpret HDR10 PNG correctly and the image will not render in HDR when pasted."); + ImGui::TextUnformatted ("Folder:"); + ImGui::SameLine (); + ImGui::TextUnformatted (_path_cache.skiv_screenshotsA); ImGui::EndTooltip (); } - ImGui::SameLine (); - ImGui::RadioButton ("Tone-map to SDR", &_registry._SnippingTonemapsHDR, 1); - if (ImGui::IsItemHovered ()) + + // HDR Tonemapping + if (HDR_Image && SKIV_HDR) { - ImGui::BeginTooltip (); - ImGui::TextUnformatted ("High Quality HDR to SDR Tone Map"); - ImGui::Separator (); - ImGui::BulletText ("Stored in the clipboard as a Bitmap for maximum compatibility with SDR software."); - ImGui::EndTooltip (); + ImGui::SameLine (); + ImGui::SeparatorEx (ImGuiSeparatorFlags_Vertical); + ImGui::SameLine (); + ImGui::BeginGroup (); + ImGui::TextColored (ImGui::GetStyleColorVec4 (ImGuiCol_TextDisabled), "HDR:"); + ImGui::SameLine (); + ImGui::RadioButton ("Keep HDR", &_registry._SnippingTonemapsHDR, 0); + if (ImGui::IsItemHovered ()) + { + ImGui::BeginTooltip (); + ImGui::TextUnformatted ("HDR PNG may have Compatibility Issues"); + ImGui::Separator (); + ImGui::BulletText ("Generally the clipboard contents can only be pasted into browser-derived software (i.e. built using Chromium, Electron) and SKIV."); + ImGui::BulletText ("Some browsers cannot interpret HDR10 PNG correctly and the image will not render in HDR when pasted."); + ImGui::EndTooltip (); + } + ImGui::SameLine (); + ImGui::RadioButton ("Tone-map to SDR", &_registry._SnippingTonemapsHDR, 1); + if (ImGui::IsItemHovered ()) + { + ImGui::BeginTooltip (); + ImGui::TextUnformatted ("High Quality HDR to SDR Tone Map"); + ImGui::Separator (); + ImGui::BulletText ("Stored in the clipboard as a Bitmap for maximum compatibility with SDR software."); + ImGui::EndTooltip (); + } + ImGui::SameLine (); + ImGui::RadioButton ("Auto", &_registry._SnippingTonemapsHDR, 2); + if (ImGui::IsItemHovered ()) + { + ImGui::BeginTooltip (); + ImGui::TextUnformatted ("Use SDR for Snips at or Below Windows SDR Desktop Luminance"); + ImGui::Separator (); + ImGui::BulletText ("For HDR range content, captures an unaltered HDR image"); + ImGui::EndTooltip (); + } + ImGui::EndGroup (); } + ImGui::SameLine (); - ImGui::RadioButton ("Auto", &_registry._SnippingTonemapsHDR, 2); - if (ImGui::IsItemHovered ()) + ImGui::SeparatorEx (ImGuiSeparatorFlags_Vertical); + ImGui::SameLine (); + + // X (Close) Button + ImGui::BeginGroup (); + ImGui::PushStyleColor (ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4 (ImGuiCol_SKIF_Failure)); + ImGui::PushStyleColor (ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4 (ImGuiCol_SKIF_Failure) * ImVec4(1.2f, 1.2f, 1.2f, 1.0f)); + + static bool closeButtonHoverActive = false; + + if (_registry._StyleLightMode && closeButtonHoverActive) + ImGui::PushStyleColor (ImGuiCol_Text, ImGui::GetStyleColorVec4 (ImGuiCol_WindowBg)); //ImVec4 (0.9F, 0.9F, 0.9F, 1.0f)); + + if (ImGui::Button (ICON_FA_XMARK, ImVec2 ( 30.0f * SKIF_ImGui_GlobalDPIScale, 0.0f ) )) // HotkeyEsc is situational + _registry._SnippingModeExit = true; + + if (_registry._StyleLightMode) { - ImGui::BeginTooltip (); - ImGui::TextUnformatted ("Use SDR for Snips at or Below Windows SDR Desktop Luminance"); - ImGui::Separator (); - ImGui::BulletText ("For HDR range content, captures an unaltered HDR image"); - ImGui::EndTooltip (); + if (closeButtonHoverActive) + ImGui::PopStyleColor ( ); + + closeButtonHoverActive = (ImGui::IsItemHovered () || ImGui::IsItemActivated ()); } - ImGui::TreePop (); + + ImGui::PopStyleColor (2); + ImGui::EndGroup (); + // End of X (Close) Button + ImGui::EndGroup (); if ( ImGui::IsWindowHovered () || ImGui::IsItemActive () ) @@ -2391,7 +2457,7 @@ wWinMain ( _In_ HINSTANCE hInstance, _registry._SnippingModeExit = true; _GetRectBelowCursor (&selection, false); capture_area = selection; - capture_area._mode = (_registry.eScreenshotsAutosave & CaptureMode_Region) ? CaptureMode_Region : CaptureMode_None; + capture_area._mode = _saveToDisk; } else if (! ImGui::IsMouseDragging (ImGuiMouseButton_Left)) @@ -2409,7 +2475,7 @@ wWinMain ( _In_ HINSTANCE hInstance, clicked = false; _registry._SnippingModeExit = true; capture_area = selection_auto; - capture_area._mode = (_registry.eScreenshotsAutosave & CaptureMode_Region) ? CaptureMode_Region : CaptureMode_None; + capture_area._mode = _saveToDisk; } else if (selection_auto._rect.Min != selection_auto._rect.Max) From 8ba6b1aea76d6031df96d18196da11e2ff6ef07e Mon Sep 17 00:00:00 2001 From: Aemony Date: Wed, 25 Mar 2026 05:26:10 +0100 Subject: [PATCH 12/23] Snipping Toolbar: Select File + Hotkeys! - Ctrl+S to toggle between saving to disk - Ctrl+E to toggle between selecting file in File Explorer - Fixed a bug that bound Encoder Setup to Ctrl+E instead of Ctrl+B --- include/utility/image.h | 9 ++-- src/SKIV.cpp | 94 ++++++++++++++++++++++++++++------------- src/tabs/viewer.cpp | 5 ++- src/utility/image.cpp | 57 ++++++++++++++----------- 4 files changed, 104 insertions(+), 61 deletions(-) diff --git a/include/utility/image.h b/include/utility/image.h index 76881a0..06c3b82 100644 --- a/include/utility/image.h +++ b/include/utility/image.h @@ -135,12 +135,13 @@ static const ParamsPQ PQ = #pragma warning( pop ) -struct SKIV_Region { +struct SKIV_CaptureData { ImRect _rect; std::wstring _title; CaptureMode _mode; + bool _select = false; - SKIV_Region (ImRect r_, std::wstring t_, CaptureMode m_) + SKIV_CaptureData (ImRect r_, std::wstring t_, CaptureMode m_) { _rect = r_; _title = t_; @@ -155,11 +156,11 @@ float SKIV_Image_LinearToPQY (float N); DirectX::XMVECTOR SKIV_Image_Rec709toICtCp (DirectX::XMVECTOR N); DirectX::XMVECTOR SKIV_Image_ICtCptoRec709 (DirectX::XMVECTOR N); -bool SKIV_Image_CopyToClipboard (const DirectX::Image* pImage, bool isHDR, CaptureMode mode, const wchar_t* wszFileName); +bool SKIV_Image_CopyToClipboard (const DirectX::Image* pImage, bool isHDR, SKIV_CaptureData capture_data); HRESULT SKIV_Image_SaveToDisk_HDR (const DirectX::Image& image, const wchar_t* wszFileName); HRESULT SKIV_Image_SaveToDisk_SDR (const DirectX::Image& image, const wchar_t* wszFileName, bool force_sRGB); HRESULT SKIV_Image_CaptureDesktop (DirectX::ScratchImage& image, POINT pos, int flags = 0x0); -void SKIV_Image_CaptureRegion (SKIV_Region capture_area); +void SKIV_Image_CaptureRegion (SKIV_CaptureData capture_data); HRESULT SKIV_Image_TonemapToSDR (const DirectX::Image& image, DirectX::ScratchImage& final_sdr, float mastering_max_nits, float mastering_sdr_nits); bool SKIV_Image_IsUltraHDR (const wchar_t* wszFileName); diff --git a/src/SKIV.cpp b/src/SKIV.cpp index 13a0be0..5d7d6b0 100644 --- a/src/SKIV.cpp +++ b/src/SKIV.cpp @@ -1459,9 +1459,10 @@ wWinMain ( _In_ HINSTANCE hInstance, hotkeyCtrlF = false, // Toggle Fullscreen Mode hotkeyCtrlV = false, // Paste data through the clipboard hotkeyCtrlN = false, // Minimize app - hotkeyCtrlS = false, + hotkeyCtrlS = false, // Viewer: Save Current Image (in same Dynamic Range), Snipping Mode: Toggle save to Disk hotkeyCtrlX = false, - hotkeyCtrlB = false; // Encoder Config + hotkeyCtrlB = false, // Encoder Config + hotkeyCtrlE = false; // Snipping Mode: Toggle browse to folder // Handled in viewer.cpp //hotkeyCtrl1 = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_1 )->DownDuration == 0.0f), // Viewer -> Image Scaling: View actual size (1:1 / None) @@ -1711,9 +1712,10 @@ wWinMain ( _In_ HINSTANCE hInstance, hotkeyCtrlF = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_F )->DownDuration == 0.0f); // Toggle Fullscreen Mode hotkeyCtrlV = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_V )->DownDuration == 0.0f); // Paste data through the clipboard hotkeyCtrlN = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_N )->DownDuration == 0.0f); // Minimize app - hotkeyCtrlS = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_S )->DownDuration == 0.0f); // Save Current Image (in same Dynamic Range) + hotkeyCtrlS = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_S )->DownDuration == 0.0f); // Viewer: Save Current Image (in same Dynamic Range), Snipping Mode: Toggle save to Disk hotkeyCtrlX = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_X )->DownDuration == 0.0f); // Export Current Image (HDR -> SDR) - hotkeyCtrlB = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_E )->DownDuration == 0.0f); // Configure Image Encoders + hotkeyCtrlB = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_B )->DownDuration == 0.0f); // Configure Image Encoders + hotkeyCtrlE = (io.KeyCtrl && ImGui::GetKeyData (ImGuiKey_E )->DownDuration == 0.0f); // Snipping Toolbar: Toggle browse to folder const bool hotkeyCycleScaling = ImGui::IsKeyPressed (ImGuiKey_GamepadL3); const bool hotkeyCycleVisualization = ImGui::IsKeyPressed (ImGuiKey_GamepadR3); @@ -1954,7 +1956,7 @@ wWinMain ( _In_ HINSTANCE hInstance, RepositionSKIF = false; // Only allow navigational hotkeys when in Large Mode and as long as no popups are opened - if (! SKIF_ImGui_IsAnyPopupOpen ( )) + if (! SKIF_ImGui_IsAnyPopupOpen ( ) && ! _registry._SnippingMode) { if (hotkeyF1) { @@ -2129,7 +2131,7 @@ wWinMain ( _In_ HINSTANCE hInstance, ImRect allowable (SKIV_DesktopImage._desktop_pos, SKIV_DesktopImage._desktop_pos + resolution); - SKIV_Region capture_area = SKIV_Region (ImRect(), L"", CaptureMode_None); + SKIV_CaptureData capture_data = SKIV_CaptureData (ImRect(), L"", CaptureMode_None); bool HDR_Image = SKIV_DesktopImage._hdr_image; bool SKIV_HDR = (HDR_Image ? SKIF_ImGui_IsViewportHDR (SKIF_ImGui_hWnd) : false); @@ -2162,8 +2164,8 @@ wWinMain ( _In_ HINSTANCE hInstance, draw_list->AddRectFilled (allowable.Min, allowable.Max, ImGui::GetColorU32 (IM_COL32 (0, 0, 0, 20))); } - static SKIV_Region selection = SKIV_Region (ImRect(), L"Desktop_Region", CaptureMode_Region); - static SKIV_Region selection_auto = SKIV_Region (ImRect(), L"Desktop_Auto", CaptureMode_Region); + static SKIV_CaptureData selection = SKIV_CaptureData (ImRect(), L"Desktop_Region", CaptureMode_Region); + static SKIV_CaptureData selection_auto = SKIV_CaptureData (ImRect(), L"Desktop_Auto", CaptureMode_Region); if (GetForegroundWindow () != SKIF_ImGui_hWnd) SetForegroundWindow ( SKIF_ImGui_hWnd); @@ -2204,7 +2206,7 @@ wWinMain ( _In_ HINSTANCE hInstance, return false; }; - auto _GetRectBelowCursor = [&](SKIV_Region* _region, bool isAutoSelection) -> void + auto _GetRectBelowCursor = [&](SKIV_CaptureData* _data, bool isAutoSelection) -> void { // This feature is unsupported on rotated displays // @@ -2213,8 +2215,8 @@ wWinMain ( _In_ HINSTANCE hInstance, { if (isAutoSelection) { - _region->_rect.Min = ImVec2 (0.0f, 0.0f); - _region->_rect.Max = ImVec2 (0.0f, 0.0f); + _data->_rect.Min = ImVec2 (0.0f, 0.0f); + _data->_rect.Max = ImVec2 (0.0f, 0.0f); } return; } @@ -2280,13 +2282,13 @@ wWinMain ( _In_ HINSTANCE hInstance, if (isAutoSelection) { - _region->_rect.Min.x = static_cast (rect.left); - _region->_rect.Min.y = static_cast (rect.top); - _region->_rect.Max.x = static_cast (rect.right); - _region->_rect.Max.y = static_cast (rect.bottom); + _data->_rect.Min.x = static_cast (rect.left); + _data->_rect.Min.y = static_cast (rect.top); + _data->_rect.Max.x = static_cast (rect.right); + _data->_rect.Max.y = static_cast (rect.bottom); } - _region->_title = SKIV_GetBaseFilename (hWnd); + _data->_title = SKIV_GetBaseFilename (hWnd); /* PLOG_VERBOSE << "----------------------"; @@ -2295,8 +2297,8 @@ wWinMain ( _In_ HINSTANCE hInstance, if (RealGetWindowClassW (top_most, wszRealWindowClass, 64)) PLOG_VERBOSE << "Class: " << wszRealWindowClass; PLOG_VERBOSE << "Pos: " << point.x << "," << point.y; - PLOG_VERBOSE << "Min: " << _region.Min.x << "," << _region.Min.y; - PLOG_VERBOSE << "Max: " << _region.Max.x << "," << _region.Max.y; + PLOG_VERBOSE << "Min: " << _data.Min.x << "," << _data.Min.y; + PLOG_VERBOSE << "Max: " << _data.Max.x << "," << _data.Max.y; */ breakLoop = true; @@ -2312,6 +2314,7 @@ wWinMain ( _In_ HINSTANCE hInstance, static bool clicked = false; static bool _saveToDisk = false; + static bool _selectFile = false; if (_registry._SnippingModeInit) { _registry._SnippingModeInit = false; @@ -2350,16 +2353,21 @@ wWinMain ( _In_ HINSTANCE hInstance, ImGui::PopStyleColor (); ImGui::Spacing (); + ImGui::SameLine (); // Save To Disk + if (hotkeyCtrlS) + _saveToDisk = ! _saveToDisk; + ImGui::PushStyleColor (ImGuiCol_Text, ImGui::GetStyleColorVec4 (ImGuiCol_SKIF_Info)); - ImGui::SameLine (); ImGui::Checkbox (" " ICON_FA_FLOPPY_DISK "###ToolbarSaveToDisk", &_saveToDisk); ImGui::PopStyleColor (); if (ImGui::IsItemHovered ()) { ImGui::BeginTooltip (); ImGui::TextUnformatted ("Save captured screenshot?"); + ImGui::SameLine (); + ImGui::TextColored (ImGui::GetStyleColorVec4 (ImGuiCol_TextDisabled), "Ctrl+S"); ImGui::Separator (); ImGui::TextUnformatted ("Folder:"); ImGui::SameLine (); @@ -2367,6 +2375,30 @@ wWinMain ( _In_ HINSTANCE hInstance, ImGui::EndTooltip (); } + ImGui::SameLine (); + + // Show File In Explorer + if (! _saveToDisk) + SKIF_ImGui_PushDisableState (); + else if (hotkeyCtrlE) + _selectFile = ! _selectFile; + + static bool _tOpenFolderDisabled = false; + ImGui::PushStyleColor (ImGuiCol_Text, ImColor(255, 207, 72).Value); + ImGui::Checkbox (" " ICON_FA_FOLDER_OPEN "###ToolbarOpenFolder", &_selectFile); + ImGui::PopStyleColor (); + if (ImGui::IsItemHovered ()) + { + ImGui::BeginTooltip (); + ImGui::TextUnformatted ("Open folder after capture?"); + ImGui::SameLine (); + ImGui::TextColored (ImGui::GetStyleColorVec4 (ImGuiCol_TextDisabled), "Ctrl+E"); + ImGui::EndTooltip (); + } + + if (! _saveToDisk) + SKIF_ImGui_PopDisableState (); + // HDR Tonemapping if (HDR_Image && SKIV_HDR) { @@ -2455,9 +2487,10 @@ wWinMain ( _In_ HINSTANCE hInstance, if (! clicked && SKIF_ImGui_SelectionRect (&selection._rect, allowable, 0, SelectionFlag_Filled)) { _registry._SnippingModeExit = true; - _GetRectBelowCursor (&selection, false); - capture_area = selection; - capture_area._mode = _saveToDisk; + _GetRectBelowCursor (&selection, false); + capture_data = selection; + capture_data._mode = _saveToDisk; + capture_data._select = _selectFile; } else if (! ImGui::IsMouseDragging (ImGuiMouseButton_Left)) @@ -2474,8 +2507,9 @@ wWinMain ( _In_ HINSTANCE hInstance, { clicked = false; _registry._SnippingModeExit = true; - capture_area = selection_auto; - capture_area._mode = _saveToDisk; + capture_data = selection_auto; + capture_data._mode = _saveToDisk; + capture_data._select = _selectFile; } else if (selection_auto._rect.Min != selection_auto._rect.Max) @@ -2491,13 +2525,13 @@ wWinMain ( _In_ HINSTANCE hInstance, else clicked = false; - if (capture_area._rect.GetArea() != 0) + if (capture_data._rect.GetArea() != 0) { ignoredWindows.clear(); PLOG_VERBOSE << "Attempting to capture region..."; - SKIV_Image_CaptureRegion (capture_area); + SKIV_Image_CaptureRegion (capture_data); } } #pragma endregion @@ -4208,8 +4242,8 @@ SKIF_WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) if (mode == CaptureMode_Window) { - const SKIV_Region region = - SKIV_Region ( + const SKIV_CaptureData region = + SKIV_CaptureData ( ImRect (static_cast (capture_rect.left ), static_cast (capture_rect.top ), static_cast (capture_rect.right ), @@ -4230,8 +4264,8 @@ SKIF_WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) else if (mode == CaptureMode_Screen) { extern skiv_image_desktop_s SKIV_DesktopImage; - const SKIV_Region region = - SKIV_Region ( + const SKIV_CaptureData region = + SKIV_CaptureData ( ImRect (ImVec2 (0, 0), SKIV_DesktopImage._resolution), filename, diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 3a77482..12c4ffe 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -2727,7 +2727,8 @@ SKIF_UI_Tab_DrawViewer (void) SUCCEEDED (DirectX::CopyRectangle (*captured_img.GetImages (), src_rect, *subrect.GetImages (), DirectX::TEX_FILTER_DEFAULT, 0, 0))) { - if (SKIV_Image_CopyToClipboard (subrect.GetImages (), cover.is_hdr, CaptureMode_None, L"cp_rect")) + + if (SKIV_Image_CopyToClipboard (subrect.GetImages (), cover.is_hdr, { ImRect(), L"cp_rect", CaptureMode_None } )) { ImGui::InsertNotification ( { @@ -2762,7 +2763,7 @@ SKIF_UI_Tab_DrawViewer (void) else { - if (SKIV_Image_CopyToClipboard (captured_img.GetImages (), cover.is_hdr, CaptureMode_None, L"cp_full")) + if (SKIV_Image_CopyToClipboard (captured_img.GetImages (), cover.is_hdr, { ImRect(), L"cp_full", CaptureMode_None } )) { ImGui::InsertNotification ( { diff --git a/src/utility/image.cpp b/src/utility/image.cpp index d26acc0..0d4792e 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -1078,7 +1078,7 @@ SKIV_PNG_CopyToClipboard (const DirectX::Image& image, const void *pData, size_t return false; } -bool SKIV_Image_CopyToClipboard (const DirectX::Image* pImage, bool isHDR, CaptureMode mode, const wchar_t* wszFileName) +bool SKIV_Image_CopyToClipboard (const DirectX::Image* pImage, bool isHDR, SKIV_CaptureData capture_data) { using namespace DirectX; @@ -1087,9 +1087,9 @@ using namespace DirectX; static SKIF_CommonPathsCache& _path_cache = SKIF_CommonPathsCache::GetInstance ( ); - bool isPersistent = (mode != CaptureMode_None); + bool isPersistent = (capture_data._mode != CaptureMode_None); std::wstring wsPNGPath = (isPersistent) ? _path_cache.skiv_screenshots : _path_cache.skiv_temp; - std::wstring wsFilename = std::wstring (wszFileName); + std::wstring wsFilename = capture_data._title; wsFilename += L"_"; @@ -1118,7 +1118,7 @@ using namespace DirectX; wsPNGPath += wsFilename + L".png"; - PLOG_VERBOSE << wsPNGPath; + //PLOG_VERBOSE << wsPNGPath; static SKIF_RegistrySettings& _registry = SKIF_RegistrySettings::GetInstance ( ); @@ -1165,6 +1165,9 @@ using namespace DirectX; { PLOG_VERBOSE << "SKIF_Image_SaveToDisk_HDR ( ): SUCCEEDED"; + if (capture_data._select) + SKIF_Util_FileExplorer_SelectFile (wsPNGPath.c_str()); + if (SKIV_PNG_CopyToClipboard (*pImage, wsPNGPath.c_str(), 0)) { PLOG_VERBOSE << "SKIV_PNG_CopyToClipboard ( ): TRUE"; @@ -1190,8 +1193,12 @@ using namespace DirectX; if (isPersistent) { if (SUCCEEDED (SKIV_Image_SaveToDisk_SDR (*pImage, wsPNGPath.c_str(), false))) + { PLOG_VERBOSE << "SKIV_Image_SaveToDisk_SDR ( ): SUCCEEDED!"; - else + + if (capture_data._select) + SKIF_Util_FileExplorer_SelectFile (wsPNGPath.c_str()); + } else PLOG_VERBOSE << "SKIF_Image_SaveToDisk_HDR ( ): FAILED"; } @@ -3405,11 +3412,11 @@ SKIV_Image_CaptureDesktop (DirectX::ScratchImage& image, POINT point, int flags) } void -SKIV_Image_CaptureRegion (SKIV_Region capture_area) +SKIV_Image_CaptureRegion (SKIV_CaptureData capture_data) { HMONITOR hMonCaptured = - MonitorFromPoint ({ static_cast (capture_area._rect.Min.x), - static_cast (capture_area._rect.Min.y) }, MONITOR_DEFAULTTONEAREST); + MonitorFromPoint ({ static_cast (capture_data._rect.Min.x), + static_cast (capture_data._rect.Min.y) }, MONITOR_DEFAULTTONEAREST); MONITORINFO minfo = { .cbSize = sizeof (MONITORINFO) }; GetMonitorInfo (hMonCaptured, &minfo); @@ -3417,11 +3424,11 @@ SKIV_Image_CaptureRegion (SKIV_Region capture_area) // Fixes snipping rectangles on non-primary (origin != 0,0) displays auto _AdjustCaptureAreaRelativeToDisplayOrigin = [&](void) { - capture_area._rect.Min.x -= minfo.rcMonitor.left; - capture_area._rect.Max.x -= minfo.rcMonitor.left; + capture_data._rect.Min.x -= minfo.rcMonitor.left; + capture_data._rect.Max.x -= minfo.rcMonitor.left; - capture_area._rect.Min.y -= minfo.rcMonitor.top; - capture_area._rect.Max.y -= minfo.rcMonitor.top; + capture_data._rect.Min.y -= minfo.rcMonitor.top; + capture_data._rect.Max.y -= minfo.rcMonitor.top; }; _AdjustCaptureAreaRelativeToDisplayOrigin (); @@ -3434,34 +3441,34 @@ SKIV_Image_CaptureRegion (SKIV_Region capture_area) width = static_cast (minfo.rcMonitor.bottom - minfo.rcMonitor.top); - std::swap (capture_area._rect.Min.x, capture_area._rect.Min.y); - std::swap (capture_area._rect.Max.x, capture_area._rect.Max.y); + std::swap (capture_data._rect.Min.x, capture_data._rect.Min.y); + std::swap (capture_data._rect.Max.x, capture_data._rect.Max.y); const float capture_height = - static_cast (capture_area._rect.Max.y - capture_area._rect.Min.y), + static_cast (capture_data._rect.Max.y - capture_data._rect.Min.y), capture_width = - static_cast (capture_area._rect.Max.x - capture_area._rect.Min.x); + static_cast (capture_data._rect.Max.x - capture_data._rect.Min.x); if (SKIV_DesktopImage._rotation == DXGI_MODE_ROTATION_ROTATE90) { - capture_area._rect.Min.y = height - capture_area._rect.Max.y; - capture_area._rect.Max.y = height - capture_area._rect.Max.y + capture_height; + capture_data._rect.Min.y = height - capture_data._rect.Max.y; + capture_data._rect.Max.y = height - capture_data._rect.Max.y + capture_height; } else { std::ignore = capture_width; std::ignore = width; - //capture_area.Min.x = width - capture_width; - //capture_area.Max.x = width; + //capture_data.Min.x = width - capture_width; + //capture_data.Max.x = width; } } const size_t - x = static_cast (std::max (0.0f, capture_area._rect.Min.x)), - y = static_cast (std::max (0.0f, capture_area._rect.Min.y)), - width = static_cast (std::max (0.0f, capture_area._rect.GetWidth ())), - height = static_cast (std::max (0.0f, capture_area._rect.GetHeight ())); + x = static_cast (std::max (0.0f, capture_data._rect.Min.x)), + y = static_cast (std::max (0.0f, capture_data._rect.Min.y)), + width = static_cast (std::max (0.0f, capture_data._rect.GetWidth ())), + height = static_cast (std::max (0.0f, capture_data._rect.GetHeight ())); const DirectX::Rect src_rect (x,y, width,height); @@ -3520,7 +3527,7 @@ SKIV_Image_CaptureRegion (SKIV_Region capture_area) PLOG_VERBOSE << "DirectX::FlipRotate ( ): FAILED"; } - if (SKIV_Image_CopyToClipboard (final, SKIV_DesktopImage._hdr_image, capture_area._mode, capture_area._title.c_str())) + if (SKIV_Image_CopyToClipboard (final, SKIV_DesktopImage._hdr_image, capture_data)) { PLOG_VERBOSE << "SKIV_Image_CopyToClipboard ( ): SUCCEEDED"; From c3e6168aa630d2b2bdbfd2bdc0fb995abe6a267b Mon Sep 17 00:00:00 2001 From: Aemony Date: Wed, 25 Mar 2026 05:29:42 +0100 Subject: [PATCH 13/23] Fixed Snip: Select File being persistent --- src/SKIV.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SKIV.cpp b/src/SKIV.cpp index 5d7d6b0..f8673e4 100644 --- a/src/SKIV.cpp +++ b/src/SKIV.cpp @@ -2319,6 +2319,7 @@ wWinMain ( _In_ HINSTANCE hInstance, { _registry._SnippingModeInit = false; _saveToDisk = (_registry.eScreenshotsAutosave & CaptureMode_Region); + _selectFile = false; // Reset on each capture (for now) } static bool toolbar = true; // HDR_Image && SKIV_HDR From 4d2738375e8a83d3ab8a9f21803ea5e3b28e5309 Mon Sep 17 00:00:00 2001 From: Aemony Date: Wed, 25 Mar 2026 18:35:25 +0100 Subject: [PATCH 14/23] Minor improvements to notification toasts --- src/SKIV.cpp | 2 +- src/tabs/viewer.cpp | 11 +++-------- src/utility/image.cpp | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/SKIV.cpp b/src/SKIV.cpp index f8673e4..b833860 100644 --- a/src/SKIV.cpp +++ b/src/SKIV.cpp @@ -3439,7 +3439,7 @@ wWinMain ( _In_ HINSTANCE hInstance, SKIF_Util_GetClipboardBitmapData ( ); - ImGui::InsertNotification({ ImGuiToastType::Info, 3000, "Received a bitmap paste" }); + ImGui::InsertNotification({ ImGuiToastType::Info, 3000, "Received a bitmap paste", "" }); } else if ((cbd & ClipboardData_HDROP)) diff --git a/src/tabs/viewer.cpp b/src/tabs/viewer.cpp index 12c4ffe..8e207ce 100644 --- a/src/tabs/viewer.cpp +++ b/src/tabs/viewer.cpp @@ -2732,7 +2732,7 @@ SKIF_UI_Tab_DrawViewer (void) { ImGui::InsertNotification ( { - ImGuiToastType::Info, + ImGuiToastType::Success, 3000, "Copied area to clipboard", "%.fx%.f -> %.fx%.f", copyRect.Min.x, @@ -2767,7 +2767,7 @@ SKIF_UI_Tab_DrawViewer (void) { ImGui::InsertNotification ( { - ImGuiToastType::Info, + ImGuiToastType::Success, 3000, "Copied image to clipboard", "" } @@ -2991,12 +2991,7 @@ SKIF_UI_Tab_DrawViewer (void) dragDroppedFilePath = _current_folder.activeFile->path; if (results == 2) - ImGui::InsertNotification ( - { - ImGuiToastType::Info, - 5000, - "Background Scan", "Surrounding images have been refreshed!" - }); + ImGui::InsertNotification ({ ImGuiToastType::Info, 5000, "Refreshed nearby images.", "" }); } } diff --git a/src/utility/image.cpp b/src/utility/image.cpp index 0d4792e..ace29f3 100644 --- a/src/utility/image.cpp +++ b/src/utility/image.cpp @@ -3533,7 +3533,7 @@ SKIV_Image_CaptureRegion (SKIV_CaptureData capture_data) ImGui::InsertNotification ( { - ImGuiToastType::Info, + ImGuiToastType::Success, 3000, "Copied image to clipboard", "" } From 8dc8c2476fbf0f92815b9271304f50ac18b7443a Mon Sep 17 00:00:00 2001 From: Aemony Date: Wed, 25 Mar 2026 18:51:04 +0100 Subject: [PATCH 15/23] Bump SDK to stop GitHub Actions from failing --- SKIV.vcxproj | 2 +- src/utility/utility.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/SKIV.vcxproj b/SKIV.vcxproj index 8dabfa0..5ef5e5e 100644 --- a/SKIV.vcxproj +++ b/SKIV.vcxproj @@ -23,7 +23,7 @@ {93301458-CC3B-4924-B659-EB190D6EF9EB} Win32Proj SKIV - 10.0.22621.0 + 10.0 diff --git a/src/utility/utility.cpp b/src/utility/utility.cpp index a3148f5..0bc9ba1 100644 --- a/src/utility/utility.cpp +++ b/src/utility/utility.cpp @@ -23,6 +23,9 @@ #pragma comment(lib, "Userenv.lib") #pragma comment(lib, "Gdiplus.lib") +#pragma comment(lib, "RuntimeObject.lib") +#pragma comment(lib, "mincore.lib") + #include #include #include From ecc0d7edc2a18c6f1a9c96f909ed33b5aed392b2 Mon Sep 17 00:00:00 2001 From: Aemony Date: Wed, 25 Mar 2026 23:15:16 +0100 Subject: [PATCH 16/23] Disable Viewer hotkeys while on the Settings tab --- src/SKIV.cpp | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/SKIV.cpp b/src/SKIV.cpp index b833860..acc3e0c 100644 --- a/src/SKIV.cpp +++ b/src/SKIV.cpp @@ -1459,8 +1459,8 @@ wWinMain ( _In_ HINSTANCE hInstance, hotkeyCtrlF = false, // Toggle Fullscreen Mode hotkeyCtrlV = false, // Paste data through the clipboard hotkeyCtrlN = false, // Minimize app - hotkeyCtrlS = false, // Viewer: Save Current Image (in same Dynamic Range), Snipping Mode: Toggle save to Disk - hotkeyCtrlX = false, + hotkeyCtrlS = false, // Viewer: Save Current Image (in same Dynamic Range), Snipping Mode: Toggle save to Disk, Settings: Save Screenshots Pattern + hotkeyCtrlX = false, // Export Current Image (HDR -> SDR) hotkeyCtrlB = false, // Encoder Config hotkeyCtrlE = false; // Snipping Mode: Toggle browse to folder @@ -1976,31 +1976,16 @@ wWinMain ( _In_ HINSTANCE hInstance, SKIF_Tab_ChangeTo = UITab_About; } - if (allowShortcutCtrlA && (hotkeyCtrlA || hotkeyCtrlO)) + if (SKIF_Tab_Selected == UITab_Viewer && allowShortcutCtrlA) { - if (SKIF_Tab_Selected != UITab_Viewer) - SKIF_Tab_ChangeTo = UITab_Viewer; - - OpenFileDialog = PopupState_Open; - } - - if (allowShortcutCtrlA && (hotkeyCtrlX || hotkeyCtrlS)) - { - if (SKIF_Tab_Selected != UITab_Viewer) - SKIF_Tab_ChangeTo = UITab_Viewer; - + if (hotkeyCtrlA || hotkeyCtrlO) + OpenFileDialog = PopupState_Open; if (hotkeyCtrlX) ExportSDRDialog = PopupState_Open; if (hotkeyCtrlS) SaveFileDialog = PopupState_Open; - } - - if (allowShortcutCtrlA && hotkeyCtrlB) - { - if (SKIF_Tab_Selected != UITab_Viewer) - SKIF_Tab_ChangeTo = UITab_Viewer; - - ConfigEncoders = PopupState_Open; + if (hotkeyCtrlB) + ConfigEncoders = PopupState_Open; } } From 881c6b23ba22419dc7b67a1646efe53d1172d0b5 Mon Sep 17 00:00:00 2001 From: Aemony Date: Wed, 25 Mar 2026 23:16:03 +0100 Subject: [PATCH 17/23] Added configurable naming scheme for screenshots --- include/utility/registry.h | 7 +++- src/tabs/settings.cpp | 66 +++++++++++++++++++++++++++++++++++++- src/utility/image.cpp | 38 +++++++++++----------- src/utility/registry.cpp | 3 ++ 4 files changed, 94 insertions(+), 20 deletions(-) diff --git a/include/utility/registry.h b/include/utility/registry.h index beadb54..24638af 100644 --- a/include/utility/registry.h +++ b/include/utility/registry.h @@ -369,6 +369,10 @@ struct SKIF_RegistrySettings { SKIF_MakeRegKeyWS ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\)", LR"(Screenshots)" ); + KeyValue regKVScreenshotsPattern = + SKIF_MakeRegKeyWS ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\)", + LR"(Screenshots Pattern)" ); + KeyValue regKVAutoUpdateVersion = SKIF_MakeRegKeyWS ( LR"(SOFTWARE\Kaldaien\Special K\Viewer\)", LR"(Auto-Update Version)" ); @@ -470,8 +474,9 @@ struct SKIF_RegistrySettings { std::wstring wsUpdateChannel = L"Website"; // Default to stable channel std::wstring wsIgnoreUpdate; std::wstring wsPathViewer; - std::wstring wsPathScreenshots; std::wstring wsPathSpecialK; + std::wstring wsPathScreenshots; + std::wstring wsScreenshotsPattern = L"__