mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-11 04:38:14 -06:00
Enable "Space" only to activate Peek (#41867)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request Closes: #26143 This pull request introduces a new "single Space key activation" mode for the Peek PowerToy, allowing users to open Peek with just the Space key when File Explorer or the Desktop is focused. The implementation includes settings UI changes, backend logic to enforce and manage this mode, eligibility checks for activation, and telemetry. It also enhances the user experience by disabling the activation shortcut control when space mode is enabled and providing appropriate tooltips and localization. **Key changes:** ### Feature: Single Space Key Activation Mode * Added a new setting (`EnableSpaceToActivate`) to allow users to enable Peek activation using only the Space key, restricted to File Explorer or Desktop focus. When enabled, the activation shortcut is forced to bare Space and the previous shortcut is stashed (not restored on toggle-off for simplicity). (`src/modules/peek/peek/dllmain.cpp`, `src/settings-ui/Settings.UI.Library/PeekProperties.cs`, `src/settings-ui/Settings.UI/ViewModels/PeekViewModel.cs`, `src/settings-ui/Settings.UI/SettingsXAML/Views/PeekPage.xaml`, `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`) [[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R132-R169) [[2]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R79-R80) [[3]](diffhunk://#diff-d482fce7c2d0abbe2b307351ef7588378ddf34d47b31ebf71411f264dcce07faR22) [[4]](diffhunk://#diff-d482fce7c2d0abbe2b307351ef7588378ddf34d47b31ebf71411f264dcce07faR33-R35) [[5]](diffhunk://#diff-3fb87fad8b86d17fa39d2319425f78d3029e3de89e88f4040d449d6a16d9d240R228-R257) [[6]](diffhunk://#diff-f474be48688a195b3cce5b395ea6c0cbc93d7a76d228dcb5dc4fc33f36f2ce83L17-R51) [[7]](diffhunk://#diff-dada9baae540a067141b033257982d33df5a6a504e1a1d492fa2961bd04b6a03R3155-R3165) <img width="1018" height="197" alt="image" src="https://github.com/user-attachments/assets/6f9eec4a-2583-41e5-92e9-9dfbc186728a" /> * UI will hide the activation shortcut control. Attempts to change the shortcut programmatically are ignored while in this mode. (`src/settings-ui/Settings.UI/SettingsXAML/Views/PeekPage.xaml`, `src/settings-ui/Settings.UI/ViewModels/PeekViewModel.cs`, `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`) [[1]](diffhunk://#diff-f474be48688a195b3cce5b395ea6c0cbc93d7a76d228dcb5dc4fc33f36f2ce83L17-R51) [[2]](diffhunk://#diff-3fb87fad8b86d17fa39d2319425f78d3029e3de89e88f4040d449d6a16d9d240R173-R178) [[3]](diffhunk://#diff-3fb87fad8b86d17fa39d2319425f78d3029e3de89e88f4040d449d6a16d9d240R228-R257) [[4]](diffhunk://#diff-dada9baae540a067141b033257982d33df5a6a504e1a1d492fa2961bd04b6a03R3155-R3165) <img width="1014" height="116" alt="image" src="https://github.com/user-attachments/assets/d1513101-a859-4b06-9252-2e707bce6689" /> ### Activation Logic & Eligibility * Implemented a foreground window hook and debounce logic to determine if Peek can be activated by Space (only when File Explorer, Desktop, or Peek itself is focused). This minimizes CPU overhead when user repeatedly presses Space but not for Peek . (`src/modules/peek/peek/dllmain.cpp`) [[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R50-R60) [[2]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R188-R292) [[3]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039L457-R637) * Managed hook installation and cleanup based on Peek's enabled state and the space mode toggle. (`src/modules/peek/peek/dllmain.cpp`) [[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R188-R292) [[2]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R562) [[3]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R593) [[4]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R496) ### Settings & Telemetry * Added the new toggle to the settings serialization and XAML UI, with localization and descriptions. (`src/modules/peek/peek/dllmain.cpp`, `src/settings-ui/Settings.UI/SettingsXAML/Views/PeekPage.xaml`, `src/settings-ui/Settings.UI/Strings/en-us/Resources.resw`) [[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R530) [[2]](diffhunk://#diff-f474be48688a195b3cce5b395ea6c0cbc93d7a76d228dcb5dc4fc33f36f2ce83L17-R51) [[3]](diffhunk://#diff-dada9baae540a067141b033257982d33df5a6a504e1a1d492fa2961bd04b6a03R3155-R3165) * Added telemetry event for enabling/disabling space mode. (`src/modules/peek/peek/trace.cpp`, `src/modules/peek/peek/trace.h`) [[1]](diffhunk://#diff-db76a3e6fa1cc19889492b72d0c063835bdc8f67909cb9d91c9e7e47e248a87aR51-R60) [[2]](diffhunk://#diff-8f824b0a7dd76f7fcd4a15b7885233b5b3212403a56c4efd67b83c4c2d02e486R18-R20) ### Code Quality * Refactored includes and initialization logic for clarity and maintainability. (`src/modules/peek/peek/dllmain.cpp`) [[1]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039L2-R14) [[2]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R37-R39) [[3]](diffhunk://#diff-ac3987a14b7c287a047f57613d97a78265f0dcef56084fb5361021953328b039R483) These changes collectively provide a safer, more accessible, and user-friendly way to activate Peek with a single key, while ensuring users are clearly informed and accidental activations are minimized. <!-- Please review the items on the PR checklist before submitting--> --------- Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
parent
d07f40eec3
commit
08a3ae2dee
@ -19,7 +19,9 @@ namespace ManagedCommon
|
||||
private static readonly string Error = "Error";
|
||||
private static readonly string Warning = "Warning";
|
||||
private static readonly string Info = "Info";
|
||||
#if DEBUG
|
||||
private static readonly string Debug = "Debug";
|
||||
#endif
|
||||
private static readonly string TraceFlag = "Trace";
|
||||
|
||||
private static readonly string Version = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version ?? "Unknown";
|
||||
@ -151,7 +153,9 @@ namespace ManagedCommon
|
||||
|
||||
public static void LogDebug(string message, [System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
{
|
||||
#if DEBUG
|
||||
Log(message, Debug, memberName, sourceFilePath, sourceLineNumber);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LogTrace([System.Runtime.CompilerServices.CallerMemberName] string memberName = "", [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "", [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
|
||||
|
||||
@ -115,19 +115,55 @@ namespace Peek.UI.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the caret is visible in the specified window.
|
||||
/// Heuristic to decide whether the user is actively typing so we should suppress Peek activation.
|
||||
/// Current logic:
|
||||
/// - If the focused control class name contains "Edit" or "Input" (e.g. Explorer search box or in-place rename), return true.
|
||||
/// - Otherwise fall back to the legacy GUI_CARETBLINKING flag (covers other text contexts where class name differs but caret blinks).
|
||||
/// - If we fail to retrieve GUI thread info, we default to false (do not suppress) to avoid blocking activation due to transient failures.
|
||||
/// NOTE: This intentionally no longer walks ancestor chains; any Edit/Input focus inside the same top-level Explorer/Desktop window is treated as typing.
|
||||
/// </summary>
|
||||
private static bool CaretVisible(HWND hwnd)
|
||||
private static unsafe bool CaretVisible(HWND hwnd)
|
||||
{
|
||||
GUITHREADINFO guiThreadInfo = new() { cbSize = (uint)Marshal.SizeOf<GUITHREADINFO>() };
|
||||
|
||||
// Get information for the foreground thread
|
||||
if (PInvoke_PeekUI.GetGUIThreadInfo(0, ref guiThreadInfo))
|
||||
GUITHREADINFO gi = new() { cbSize = (uint)Marshal.SizeOf<GUITHREADINFO>() };
|
||||
if (!PInvoke_PeekUI.GetGUIThreadInfo(0, ref gi))
|
||||
{
|
||||
return guiThreadInfo.hwndActive == hwnd && (guiThreadInfo.flags & GUITHREADINFO_FLAGS.GUI_CARETBLINKING) != 0;
|
||||
return false; // fail open (allow activation)
|
||||
}
|
||||
|
||||
return false;
|
||||
// Quick sanity: restrict to same top-level window (match prior behavior)
|
||||
if (gi.hwndActive != hwnd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
HWND focus = gi.hwndFocus;
|
||||
if (focus == HWND.Null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get focused window class (96 chars buffer; GetClassNameW bounds writes). Treat any class containing
|
||||
// "Edit" or "Input" as a text field (search / titlebar) and suppress Peek.
|
||||
Span<char> buf = stackalloc char[96];
|
||||
fixed (char* p = buf)
|
||||
{
|
||||
int len = PInvoke_PeekUI.GetClassName(focus, p, buf.Length);
|
||||
if (len > 0)
|
||||
{
|
||||
var focusClass = new string(p, 0, len);
|
||||
if (focusClass.Contains("Edit", StringComparison.OrdinalIgnoreCase) || focusClass.Contains("Input", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true; // treat any Edit/Input focus as typing.
|
||||
}
|
||||
else
|
||||
{
|
||||
ManagedCommon.Logger.LogDebug($"Peek suppression: focus class{focusClass}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: original caret blinking heuristic for other text-entry contexts
|
||||
return (gi.flags & GUITHREADINFO_FLAGS.GUI_CARETBLINKING) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
#include "pch.h"
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "trace.h"
|
||||
#include <atlbase.h>
|
||||
#include <atomic>
|
||||
#include <comdef.h>
|
||||
#include <common/interop/shared_constants.h>
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include "trace.h"
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <filesystem>
|
||||
#include <common/interop/shared_constants.h>
|
||||
#include <atlbase.h>
|
||||
#include <exdisp.h>
|
||||
#include <comdef.h>
|
||||
#include <common/utils/elevation.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <exdisp.h>
|
||||
#include <filesystem>
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
@ -32,6 +34,9 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Forward declare global Peek so anonymous namespace uses same type
|
||||
class Peek;
|
||||
|
||||
namespace
|
||||
{
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
@ -42,6 +47,17 @@ namespace
|
||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
|
||||
const wchar_t JSON_KEY_ALWAYS_RUN_NOT_ELEVATED[] = L"AlwaysRunNotElevated";
|
||||
const wchar_t JSON_KEY_ENABLE_SPACE_TO_ACTIVATE[] = L"EnableSpaceToActivate";
|
||||
|
||||
// Space activation (single-space mode) state
|
||||
std::atomic_bool g_foregroundHookActive{ false }; // Foreground hook installed
|
||||
std::atomic_bool g_foregroundEligible{ false }; // Cached eligibility (Explorer/Desktop/Peek focused)
|
||||
HWINEVENTHOOK g_foregroundHook = nullptr; // Foreground change hook handle
|
||||
constexpr DWORD FOREGROUND_DEBOUNCE_MS = 40; // Delay before eligibility recompute (ms)
|
||||
HANDLE g_foregroundDebounceTimer = nullptr; // One-shot scheduled timer
|
||||
std::atomic<DWORD> g_foregroundLastScheduleTick{ 0 }; // Tick count when timer last scheduled
|
||||
|
||||
Peek* g_instance = nullptr; // pointer to active instance (global Peek)
|
||||
}
|
||||
|
||||
// The PowerToy name that will be shown in the settings.
|
||||
@ -60,6 +76,7 @@ private:
|
||||
|
||||
// If we should always try to run Peek non-elevated.
|
||||
bool m_alwaysRunNotElevated = true;
|
||||
bool m_enableSpaceToActivate = false; // toggle from settings
|
||||
|
||||
HANDLE m_hProcess = 0;
|
||||
DWORD m_processPid = 0;
|
||||
@ -111,11 +128,55 @@ private:
|
||||
|
||||
m_alwaysRunNotElevated = true;
|
||||
}
|
||||
try
|
||||
{
|
||||
auto jsonEnableSpaceObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ENABLE_SPACE_TO_ACTIVATE);
|
||||
m_enableSpaceToActivate = jsonEnableSpaceObject.GetNamedBoolean(L"value");
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
m_enableSpaceToActivate = false;
|
||||
}
|
||||
|
||||
// Enforce design: if space toggle ON, force single-space hotkey and store previous combination once.
|
||||
if (m_enableSpaceToActivate)
|
||||
{
|
||||
if (!(m_hotkey.win || m_hotkey.alt || m_hotkey.shift || m_hotkey.ctrl) && m_hotkey.key == ' ')
|
||||
{
|
||||
// already single space
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hotkey.win = false;
|
||||
m_hotkey.alt = false;
|
||||
m_hotkey.shift = false;
|
||||
m_hotkey.ctrl = false;
|
||||
m_hotkey.key = ' ';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If toggle off and current hotkey is bare space, revert to default (simplified policy)
|
||||
if (!(m_hotkey.win || m_hotkey.alt || m_hotkey.shift || m_hotkey.ctrl) && m_hotkey.key == ' ')
|
||||
{
|
||||
set_default_key_settings();
|
||||
}
|
||||
}
|
||||
|
||||
manage_space_mode_hook();
|
||||
Trace::SpaceModeEnabled(m_enableSpaceToActivate);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info("Peek settings are empty");
|
||||
set_default_key_settings();
|
||||
// First-run (no existing settings file or empty JSON): default to Space-only activation
|
||||
Logger::info("Peek settings are empty - initializing first-run defaults (Space activation)");
|
||||
m_enableSpaceToActivate = true;
|
||||
m_hotkey.win = false;
|
||||
m_hotkey.alt = false;
|
||||
m_hotkey.shift = false;
|
||||
m_hotkey.ctrl = false;
|
||||
m_hotkey.key = ' ';
|
||||
Trace::SpaceModeEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,6 +190,111 @@ private:
|
||||
m_hotkey.key = ' ';
|
||||
}
|
||||
|
||||
// Eligibility recompute (debounced via timer)
|
||||
public: // callable from anonymous namespace helper
|
||||
void recompute_space_mode_eligibility()
|
||||
{
|
||||
if (!m_enableSpaceToActivate)
|
||||
{
|
||||
g_foregroundEligible.store(false, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
const bool eligible = is_peek_or_explorer_or_desktop_window_focused();
|
||||
g_foregroundEligible.store(eligible, std::memory_order_relaxed);
|
||||
Logger::debug(L"Peek space-mode eligibility recomputed: {}", eligible);
|
||||
}
|
||||
|
||||
private:
|
||||
static void CALLBACK ForegroundDebounceTimerProc(PVOID /*param*/, BOOLEAN /*fired*/)
|
||||
{
|
||||
if (!g_instance || !g_foregroundHookActive.load(std::memory_order_relaxed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
g_instance->recompute_space_mode_eligibility();
|
||||
}
|
||||
|
||||
static void CALLBACK ForegroundWinEventProc(HWINEVENTHOOK /*hook*/, DWORD /*event*/, HWND /*hwnd*/, LONG /*idObject*/, LONG /*idChild*/, DWORD /*thread*/, DWORD /*time*/)
|
||||
{
|
||||
if (!g_foregroundHookActive.load(std::memory_order_relaxed) || !g_instance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const DWORD now = GetTickCount();
|
||||
const DWORD last = g_foregroundLastScheduleTick.load(std::memory_order_relaxed);
|
||||
// If no timer or sufficient time since last schedule, create a new one.
|
||||
if (!g_foregroundDebounceTimer || (now - last) >= FOREGROUND_DEBOUNCE_MS || now < last)
|
||||
{
|
||||
if (g_foregroundDebounceTimer)
|
||||
{
|
||||
// Best effort: cancel previous pending timer; ignore failure.
|
||||
DeleteTimerQueueTimer(nullptr, g_foregroundDebounceTimer, INVALID_HANDLE_VALUE);
|
||||
g_foregroundDebounceTimer = nullptr;
|
||||
}
|
||||
if (CreateTimerQueueTimer(&g_foregroundDebounceTimer, nullptr, ForegroundDebounceTimerProc, nullptr, FOREGROUND_DEBOUNCE_MS, 0, WT_EXECUTEDEFAULT))
|
||||
{
|
||||
g_foregroundLastScheduleTick.store(now, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn(L"Peek failed to create foreground debounce timer");
|
||||
// Fallback: compute immediately if timer creation failed.
|
||||
g_instance->recompute_space_mode_eligibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void install_foreground_hook()
|
||||
{
|
||||
if (g_foregroundHook || !m_enableSpaceToActivate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_instance = this;
|
||||
g_foregroundHook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, nullptr, ForegroundWinEventProc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
|
||||
if (g_foregroundHook)
|
||||
{
|
||||
g_foregroundHookActive.store(true, std::memory_order_relaxed);
|
||||
recompute_space_mode_eligibility();
|
||||
}
|
||||
else
|
||||
{
|
||||
g_foregroundHookActive.store(false, std::memory_order_relaxed);
|
||||
Logger::warn(L"Peek failed to install foreground hook. Falling back to polling.");
|
||||
}
|
||||
}
|
||||
|
||||
void uninstall_foreground_hook()
|
||||
{
|
||||
if (g_foregroundHook)
|
||||
{
|
||||
UnhookWinEvent(g_foregroundHook);
|
||||
g_foregroundHook = nullptr;
|
||||
}
|
||||
if (g_foregroundDebounceTimer)
|
||||
{
|
||||
DeleteTimerQueueTimer(nullptr, g_foregroundDebounceTimer, INVALID_HANDLE_VALUE);
|
||||
g_foregroundDebounceTimer = nullptr;
|
||||
}
|
||||
g_foregroundLastScheduleTick.store(0, std::memory_order_relaxed);
|
||||
g_foregroundHookActive.store(false, std::memory_order_relaxed);
|
||||
g_foregroundEligible.store(false, std::memory_order_relaxed);
|
||||
g_instance = nullptr;
|
||||
}
|
||||
|
||||
void manage_space_mode_hook()
|
||||
{
|
||||
if (m_enableSpaceToActivate && m_enabled)
|
||||
{
|
||||
install_foreground_hook();
|
||||
}
|
||||
else
|
||||
{
|
||||
uninstall_foreground_hook();
|
||||
}
|
||||
}
|
||||
|
||||
void parse_hotkey(winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject)
|
||||
{
|
||||
try
|
||||
@ -319,6 +485,7 @@ private:
|
||||
public:
|
||||
Peek()
|
||||
{
|
||||
LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", "Peek");
|
||||
init_settings();
|
||||
|
||||
m_hInvokeEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_PEEK_SHARED_EVENT);
|
||||
@ -331,6 +498,7 @@ public:
|
||||
{
|
||||
}
|
||||
m_enabled = false;
|
||||
uninstall_foreground_hook();
|
||||
};
|
||||
|
||||
// Destroy the powertoy and free memory
|
||||
@ -364,6 +532,7 @@ public:
|
||||
// Create a Settings object.
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
settings.set_description(MODULE_DESC);
|
||||
settings.add_bool_toggle(JSON_KEY_ENABLE_SPACE_TO_ACTIVATE, L"Enable single Space key activation", m_enableSpaceToActivate);
|
||||
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
@ -395,6 +564,7 @@ public:
|
||||
launch_process();
|
||||
m_enabled = true;
|
||||
Trace::EnablePeek(true);
|
||||
manage_space_mode_hook();
|
||||
}
|
||||
|
||||
// Disable the powertoy
|
||||
@ -425,6 +595,7 @@ public:
|
||||
|
||||
m_enabled = false;
|
||||
Trace::EnablePeek(false);
|
||||
uninstall_foreground_hook();
|
||||
}
|
||||
|
||||
// Returns if the powertoys is enabled
|
||||
@ -454,11 +625,21 @@ public:
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"Peek hotkey pressed");
|
||||
|
||||
// Only activate and consume the shortcut if a Peek, explorer or desktop window is the foreground application.
|
||||
if (is_peek_or_explorer_or_desktop_window_focused())
|
||||
bool spaceMode = m_enableSpaceToActivate && !(m_hotkey.win || m_hotkey.alt || m_hotkey.shift || m_hotkey.ctrl) && m_hotkey.key == ' ';
|
||||
bool eligible = false;
|
||||
if (spaceMode && g_foregroundHookActive.load(std::memory_order_relaxed))
|
||||
{
|
||||
eligible = g_foregroundEligible.load(std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
eligible = is_peek_or_explorer_or_desktop_window_focused();
|
||||
}
|
||||
|
||||
if (eligible)
|
||||
{
|
||||
Logger::trace(L"Peek hotkey pressed and eligible for launching");
|
||||
|
||||
// TODO: fix VK_SPACE DestroyWindow in viewer app
|
||||
if (!is_viewer_running())
|
||||
{
|
||||
@ -468,7 +649,16 @@ public:
|
||||
SetEvent(m_hInvokeEvent);
|
||||
|
||||
Trace::PeekInvoked();
|
||||
return true;
|
||||
|
||||
|
||||
if (spaceMode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -48,3 +48,13 @@ void Trace::SettingsTelemetry(PowertoyModuleIface::Hotkey& hotkey) noexcept
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingWideString(hotKeyStr.c_str(), "HotKey"));
|
||||
}
|
||||
|
||||
void Trace::SpaceModeEnabled(bool enabled) noexcept
|
||||
{
|
||||
TraceLoggingWriteWrapper(
|
||||
g_hProvider,
|
||||
"Peek_SpaceModeEnabled",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingBoolean(enabled, "Enabled"));
|
||||
}
|
||||
|
||||
@ -15,4 +15,7 @@ public:
|
||||
// Event to send settings telemetry.
|
||||
static void Trace::SettingsTelemetry(PowertoyModuleIface::Hotkey& hotkey) noexcept;
|
||||
|
||||
// Space mode telemetry (single-key activation toggle)
|
||||
static void SpaceModeEnabled(bool enabled) noexcept;
|
||||
|
||||
};
|
||||
|
||||
@ -19,6 +19,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
AlwaysRunNotElevated = new BoolProperty(true);
|
||||
CloseAfterLosingFocus = new BoolProperty(false);
|
||||
ConfirmFileDelete = new BoolProperty(true);
|
||||
EnableSpaceToActivate = new BoolProperty(true); // Toggle is ON by default for new users. No impact on existing users.
|
||||
}
|
||||
|
||||
public HotkeySettings ActivationShortcut { get; set; }
|
||||
@ -29,6 +30,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
|
||||
public BoolProperty ConfirmFileDelete { get; set; }
|
||||
|
||||
public BoolProperty EnableSpaceToActivate { get; set; }
|
||||
|
||||
public override string ToString() => JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
public class PeekSettings : BasePTModuleSettings, ISettingsConfig, IHotkeyConfig
|
||||
{
|
||||
public const string ModuleName = "Peek";
|
||||
public const string ModuleVersion = "0.0.1";
|
||||
public const string InitialModuleVersion = "0.0.1";
|
||||
public const string SpaceActivationIntroducedVersion = "0.0.2";
|
||||
public const string CurrentModuleVersion = SpaceActivationIntroducedVersion;
|
||||
|
||||
private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
@ -28,7 +30,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
public PeekSettings()
|
||||
{
|
||||
Name = ModuleName;
|
||||
Version = ModuleVersion;
|
||||
Version = CurrentModuleVersion;
|
||||
Properties = new PeekProperties();
|
||||
}
|
||||
|
||||
@ -54,6 +56,14 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
|
||||
public bool UpgradeSettingsConfiguration()
|
||||
{
|
||||
if (string.IsNullOrEmpty(Version) ||
|
||||
Version.Equals(InitialModuleVersion, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Version = CurrentModuleVersion;
|
||||
Properties.EnableSpaceToActivate.Value = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -22,11 +22,19 @@
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:GPOInfoControl>
|
||||
|
||||
<controls:SettingsGroup x:Uid="Peek_Activation_GroupSettings" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="Peek_ActivationMethod">
|
||||
<ComboBox SelectedIndex="{x:Bind ViewModel.EnableSpaceToActivate, Mode=TwoWay, Converter={StaticResource BoolToComboBoxIndexConverter}}">
|
||||
<ComboBoxItem x:Uid="Peek_ActivationMethod_CustomizedShortcut" />
|
||||
<ComboBoxItem x:Uid="Peek_ActivationMethod_SpaceBar" />
|
||||
</ComboBox>
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard
|
||||
Name="ActivationShortcut"
|
||||
x:Uid="Activation_Shortcut"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
Visibility="{x:Bind ViewModel.EnableSpaceToActivate, Mode=OneWay, Converter={StaticResource ReverseBoolToVisibilityConverter}}">
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
</controls:SettingsGroup>
|
||||
|
||||
@ -3152,6 +3152,19 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
||||
<data name="Peek_ConfirmFileDelete.Description" xml:space="preserve">
|
||||
<value>You'll be asked to confirm before files are moved to the Recycle Bin</value>
|
||||
</data>
|
||||
<data name="Peek_ActivationMethod.Header" xml:space="preserve">
|
||||
<value>Activation method</value>
|
||||
</data>
|
||||
<data name="Peek_ActivationMethod.Description" xml:space="preserve">
|
||||
<value>Use a shortcut or press the Spacebar when a file is selected</value>
|
||||
<comment>Spacebar is a physical keyboard key</comment>
|
||||
</data>
|
||||
<data name="Peek_ActivationMethod_CustomizedShortcut.Content" xml:space="preserve">
|
||||
<value>Custom shortcut</value>
|
||||
</data>
|
||||
<data name="Peek_ActivationMethod_SpaceBar.Content" xml:space="preserve">
|
||||
<value>Spacebar</value>
|
||||
</data>
|
||||
<data name="FancyZones_DisableRoundCornersOnWindowSnap.Content" xml:space="preserve">
|
||||
<value>Disable rounded corners when a window is snapped</value>
|
||||
</data>
|
||||
|
||||
@ -170,6 +170,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
if (_peekSettings.Properties.ActivationShortcut != value)
|
||||
{
|
||||
// If space mode toggle is on, ignore external attempts to change (UI will be disabled, but defensive).
|
||||
if (EnableSpaceToActivate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_peekSettings.Properties.ActivationShortcut = value ?? _peekSettings.Properties.DefaultActivationShortcut;
|
||||
OnPropertyChanged(nameof(ActivationShortcut));
|
||||
NotifySettingsChanged();
|
||||
@ -219,6 +225,33 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnableSpaceToActivate
|
||||
{
|
||||
get => _peekSettings.Properties.EnableSpaceToActivate.Value;
|
||||
set
|
||||
{
|
||||
if (_peekSettings.Properties.EnableSpaceToActivate.Value != value)
|
||||
{
|
||||
_peekSettings.Properties.EnableSpaceToActivate.Value = value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
// Force single space (0x20) without modifiers.
|
||||
_peekSettings.Properties.ActivationShortcut = new HotkeySettings(false, false, false, false, 0x20);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Revert to default (design simplification, not restoring previous custom combo).
|
||||
_peekSettings.Properties.ActivationShortcut = _peekSettings.Properties.DefaultActivationShortcut;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(EnableSpaceToActivate));
|
||||
OnPropertyChanged(nameof(ActivationShortcut));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SourceCodeWrapText
|
||||
{
|
||||
get => _peekPreviewSettings.SourceCodeWrapText.Value;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user