Skip to content

Commit 2ba5686

Browse files
committed
Add low memory mode for supported utilities
1 parent 1744bde commit 2ba5686

37 files changed

Lines changed: 997 additions & 85 deletions

File tree

src/common/SettingsAPI/settings_helpers.cpp

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#include "pch.h"
22
#include "settings_helpers.h"
33

4+
#include <algorithm>
5+
#include <mutex>
6+
47
namespace PTSettingsHelper
58
{
69
constexpr inline const wchar_t* settings_filename = L"\\settings.json";
@@ -11,6 +14,28 @@ namespace PTSettingsHelper
1114
constexpr inline const wchar_t* DataDiagnosticsRegKey = L"Software\\Classes\\PowerToys";
1215
constexpr inline const wchar_t* DataDiagnosticsRegValueName = L"AllowDataDiagnostics";
1316

17+
namespace
18+
{
19+
std::mutex low_memory_settings_mutex;
20+
json::JsonObject low_memory_settings_cache;
21+
bool low_memory_settings_cache_initialized = false;
22+
23+
json::JsonObject get_low_memory_settings_from_general_settings(const json::JsonObject& general_settings)
24+
{
25+
auto low_memory_settings = json::has(general_settings, low_memory_modules_json_field_name, json::JsonValueType::Object) ?
26+
general_settings.GetNamedObject(low_memory_modules_json_field_name) :
27+
create_default_low_memory_module_settings();
28+
ensure_low_memory_settings_shape(low_memory_settings);
29+
return low_memory_settings;
30+
}
31+
32+
json::JsonObject get_cached_low_memory_settings()
33+
{
34+
std::lock_guard lock{ low_memory_settings_mutex };
35+
return low_memory_settings_cache_initialized ? low_memory_settings_cache : create_default_low_memory_module_settings();
36+
}
37+
}
38+
1439
std::wstring get_root_save_folder_location()
1540
{
1641
PWSTR local_app_path;
@@ -99,6 +124,81 @@ namespace PTSettingsHelper
99124
return result.wstring();
100125
}
101126

127+
json::JsonObject create_default_low_memory_module_settings()
128+
{
129+
json::JsonObject obj;
130+
ensure_low_memory_settings_shape(obj);
131+
return obj;
132+
}
133+
134+
void refresh_low_memory_settings_cache()
135+
{
136+
try
137+
{
138+
refresh_low_memory_settings_cache(load_general_settings());
139+
}
140+
catch (...)
141+
{
142+
std::lock_guard lock{ low_memory_settings_mutex };
143+
if (!low_memory_settings_cache_initialized)
144+
{
145+
low_memory_settings_cache = create_default_low_memory_module_settings();
146+
low_memory_settings_cache_initialized = true;
147+
}
148+
}
149+
}
150+
151+
void refresh_low_memory_settings_cache(const json::JsonObject& general_settings)
152+
{
153+
auto low_memory_settings = get_low_memory_settings_from_general_settings(general_settings);
154+
155+
std::lock_guard lock{ low_memory_settings_mutex };
156+
low_memory_settings_cache = low_memory_settings;
157+
low_memory_settings_cache_initialized = true;
158+
}
159+
160+
void ensure_low_memory_settings_shape(json::JsonObject& obj)
161+
{
162+
for (const auto module_key : supported_low_memory_modules)
163+
{
164+
if (!json::has(obj, module_key, json::JsonValueType::Boolean))
165+
{
166+
obj.SetNamedValue(module_key, json::value(false));
167+
}
168+
}
169+
}
170+
171+
bool is_any_low_memory_module_enabled(const json::JsonObject& low_memory_settings)
172+
{
173+
for (const auto module_key : supported_low_memory_modules)
174+
{
175+
if (low_memory_settings.GetNamedBoolean(module_key, false))
176+
{
177+
return true;
178+
}
179+
}
180+
181+
return false;
182+
}
183+
184+
bool is_low_memory_module(std::wstring_view module_key)
185+
{
186+
return std::find(supported_low_memory_modules.begin(), supported_low_memory_modules.end(), module_key) != supported_low_memory_modules.end();
187+
}
188+
189+
bool is_low_memory_mode_enabled(std::wstring_view powertoy_key, bool default_value)
190+
{
191+
try
192+
{
193+
auto low_memory_modules = get_cached_low_memory_settings();
194+
return low_memory_modules.GetNamedBoolean(std::wstring{ powertoy_key }, default_value);
195+
}
196+
catch (...)
197+
{
198+
return default_value;
199+
}
200+
}
201+
102202
bool get_oobe_opened_state()
103203
{
104204
std::filesystem::path oobePath(PTSettingsHelper::get_root_save_folder_location());

src/common/SettingsAPI/settings_helpers.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
#pragma once
2+
#include <array>
23
#include <string>
4+
#include <string_view>
35
#include <Shlobj.h>
46

57
#include "../utils/json.h"
68

79
namespace PTSettingsHelper
810
{
911
constexpr inline const wchar_t* log_settings_filename = L"log_settings.json";
12+
constexpr inline const wchar_t* low_memory_modules_json_field_name = L"low_memory_modules";
13+
constexpr inline std::array supported_low_memory_modules{
14+
std::wstring_view{ L"TextExtractor" },
15+
std::wstring_view{ L"ColorPicker" },
16+
std::wstring_view{ L"AdvancedPaste" },
17+
std::wstring_view{ L"Peek" },
18+
};
1019

1120
std::wstring get_powertoys_general_save_file_location();
1221
std::wstring get_module_save_file_location(std::wstring_view powertoy_key);
@@ -20,6 +29,14 @@ namespace PTSettingsHelper
2029
json::JsonObject load_general_settings();
2130
std::wstring get_log_settings_file_location();
2231

32+
json::JsonObject create_default_low_memory_module_settings();
33+
void refresh_low_memory_settings_cache();
34+
void refresh_low_memory_settings_cache(const json::JsonObject& general_settings);
35+
void ensure_low_memory_settings_shape(json::JsonObject& obj);
36+
bool is_any_low_memory_module_enabled(const json::JsonObject& low_memory_settings);
37+
bool is_low_memory_module(std::wstring_view module_key);
38+
bool is_low_memory_mode_enabled(std::wstring_view powertoy_key, bool default_value = false);
39+
2340
bool get_oobe_opened_state();
2441
void save_oobe_opened_state();
2542
std::wstring get_last_version_run();

src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ namespace AdvancedPaste
3838
/// </summary>
3939
public partial class App : Application, IDisposable
4040
{
41+
private const string ExitAfterUseArgument = "--exit-after-use";
42+
4143
public IHost Host { get; private set; }
4244

4345
public ETWTrace EtwTrace { get; private set; } = new ETWTrace();
@@ -57,6 +59,7 @@ public partial class App : Application, IDisposable
5759
private nint windowHwnd;
5860

5961
private bool disposedValue;
62+
private bool exitAfterUse;
6063

6164
/// <summary>
6265
/// Initializes a new instance of the <see cref="App"/> class.
@@ -97,6 +100,19 @@ public MainWindow GetMainWindow()
97100
return window;
98101
}
99102

103+
internal bool ExitAfterUse => exitAfterUse;
104+
105+
internal void ExitAfterUseIfNeeded()
106+
{
107+
if (!ExitAfterUse)
108+
{
109+
return;
110+
}
111+
112+
Dispose();
113+
Environment.Exit(0);
114+
}
115+
100116
public static T GetService<T>()
101117
where T : class
102118
{
@@ -115,6 +131,8 @@ public static T GetService<T>()
115131
protected override void OnLaunched(LaunchActivatedEventArgs args)
116132
{
117133
var cmdArgs = Environment.GetCommandLineArgs();
134+
exitAfterUse = cmdArgs?.Contains(ExitAfterUseArgument, StringComparer.OrdinalIgnoreCase) == true;
135+
118136
if (cmdArgs?.Length > 1)
119137
{
120138
if (int.TryParse(cmdArgs[1], out int powerToysRunnerPid))
@@ -155,10 +173,12 @@ private async Task OnNamedPipeMessage(string message)
155173
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteMarkdownMessage())
156174
{
157175
await viewModel.ExecutePasteFormatAsync(PasteFormats.Markdown, PasteActionSource.GlobalKeyboardShortcut);
176+
ExitAfterUseIfNeeded();
158177
}
159178
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteJsonMessage())
160179
{
161180
await viewModel.ExecutePasteFormatAsync(PasteFormats.Json, PasteActionSource.GlobalKeyboardShortcut);
181+
ExitAfterUseIfNeeded();
162182
}
163183
else if (messageType == PowerToys.Interop.Constants.AdvancedPasteAdditionalActionMessage())
164184
{
@@ -196,6 +216,7 @@ private async Task OnAdvancedPasteAdditionalActionHotkey(string[] messageParts)
196216
{
197217
await ShowWindow();
198218
await viewModel.ExecutePasteFormatAsync(pasteFormat, PasteActionSource.GlobalKeyboardShortcut);
219+
ExitAfterUseIfNeeded();
199220
}
200221
}
201222
}
@@ -216,6 +237,7 @@ private async Task OnAdvancedPasteCustomActionHotkey(string[] messageParts)
216237
{
217238
await ShowWindow();
218239
await viewModel.ExecuteCustomActionAsync(customActionId, PasteActionSource.GlobalKeyboardShortcut);
240+
ExitAfterUseIfNeeded();
219241
}
220242
}
221243
}

src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/MainWindow.xaml.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ private void OnActivated(object sender, WindowActivatedEventArgs args)
9090
if (_userSettings.CloseAfterLosingFocus && args.WindowActivationState == WindowActivationState.Deactivated)
9191
{
9292
Hide();
93+
(Application.Current as App)?.ExitAfterUseIfNeeded();
9394
}
9495
}
9596

@@ -116,6 +117,7 @@ private async void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventA
116117
await _optionsViewModel.CancelPasteActionAsync();
117118
Hide();
118119
args.Handled = true;
120+
(Application.Current as App)?.ExitAfterUseIfNeeded();
119121
}
120122

121123
private void Hide()

src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,8 @@ private async Task CopyPasteAndHideAsync(DataPackage package)
635635
// Delete any temp files created. A delay is needed to ensure the file is not in use by the target application -
636636
// for example, when pasting onto File Explorer, the paste operation will trigger a file copy.
637637
_ = Task.Run(() => package.GetView().TryCleanupAfterDelayAsync(TimeSpan.FromSeconds(30)));
638+
639+
(Application.Current as global::AdvancedPaste.App)?.ExitAfterUseIfNeeded();
638640
}
639641

640642
// Command to select the previous custom format

src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,17 @@ namespace
3232
}
3333
}
3434

35-
void AdvancedPasteProcessManager::start()
35+
void AdvancedPasteProcessManager::start(bool exit_after_use)
3636
{
3737
m_enabled = true;
38+
m_exit_after_use = exit_after_use;
3839
submit_task([this]() { refresh(); });
3940
}
4041

4142
void AdvancedPasteProcessManager::stop()
4243
{
4344
m_enabled = false;
45+
m_exit_after_use = false;
4446
submit_task([this]() { refresh(); });
4547
}
4648

@@ -100,7 +102,11 @@ HRESULT AdvancedPasteProcessManager::start_process(const std::wstring& pipe_name
100102
{
101103
const unsigned long powertoys_pid = GetCurrentProcessId();
102104

103-
const auto executable_args = std::format(L"{} {}", std::to_wstring(powertoys_pid), pipe_name);
105+
std::wstring executable_args = std::format(L"{} {}", std::to_wstring(powertoys_pid), pipe_name);
106+
if (m_exit_after_use)
107+
{
108+
executable_args.append(L" --exit-after-use");
109+
}
104110

105111
SHELLEXECUTEINFOW sei{ sizeof(sei) };
106112
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
@@ -109,7 +115,6 @@ HRESULT AdvancedPasteProcessManager::start_process(const std::wstring& pipe_name
109115
sei.lpParameters = executable_args.data();
110116
if (ShellExecuteExW(&sei))
111117
{
112-
Logger::trace("Successfully started Advanced Paste process");
113118
terminate_process();
114119
m_hProcess = sei.hProcess;
115120
return S_OK;
@@ -264,4 +269,4 @@ void AdvancedPasteProcessManager::send_named_pipe_message(const std::wstring& me
264269
const CString file_name(message.c_str());
265270
m_write_pipe->Write(file_name, file_name.GetLength() * sizeof(TCHAR));
266271
}
267-
}
272+
}

src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class AdvancedPasteProcessManager
1515
AdvancedPasteProcessManager(const AdvancedPasteProcessManager&) = delete;
1616
AdvancedPasteProcessManager& operator=(const AdvancedPasteProcessManager&) = delete;
1717

18-
void start();
18+
void start(bool exit_after_use = false);
1919
void stop();
2020
void send_message(const std::wstring& message_type, const std::wstring& message_arg = L"");
2121
void bring_to_front();
@@ -31,6 +31,7 @@ class AdvancedPasteProcessManager
3131

3232
OnThreadExecutor m_thread_executor; // all internal operations are done on background thread with task queue
3333
std::atomic<bool> m_enabled = false; // written on main thread, read on background thread
34+
std::atomic<bool> m_exit_after_use = false; // written on main thread, read on background thread
3435
HANDLE m_hProcess = 0;
3536
std::unique_ptr<CAtlFile> m_write_pipe;
36-
};
37+
};

src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -975,14 +975,19 @@ class AdvancedPaste : public PowertoyModuleIface
975975
Logger::trace("AdvancedPaste::enable()");
976976
Trace::AdvancedPaste_Enable(true);
977977
m_enabled = true;
978-
m_process_manager.start();
978+
PTSettingsHelper::refresh_low_memory_settings_cache();
979+
if (!PTSettingsHelper::is_low_memory_mode_enabled(get_key()))
980+
{
981+
m_process_manager.start(false);
982+
}
979983

980984
// Start listening for external trigger event so we can invoke the same logic as the hotkey.
981985
// Note: Use start() directly instead of constructor + move assignment to avoid dangling this pointer in the thread.
982986
m_triggerEventWaiter.start(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_EVENT, [this](DWORD) {
983987
// Same logic as hotkeyId == 1 (m_advanced_paste_ui_hotkey)
984988
Logger::trace(L"AdvancedPaste ShowUI event triggered");
985-
m_process_manager.start();
989+
const bool exit_after_use = PTSettingsHelper::is_low_memory_mode_enabled(get_key());
990+
m_process_manager.start(exit_after_use);
986991
m_process_manager.bring_to_front();
987992
m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE);
988993
Trace::AdvancedPaste_Invoked(L"AdvancedPasteUIEvent");
@@ -1041,8 +1046,6 @@ class AdvancedPaste : public PowertoyModuleIface
10411046
}
10421047
}
10431048

1044-
m_process_manager.start();
1045-
10461049
// hotkeyId in same order as set by get_hotkeys
10471050
if (hotkeyId == 0)
10481051
{ // m_paste_as_plain_hotkey
@@ -1058,6 +1061,9 @@ class AdvancedPaste : public PowertoyModuleIface
10581061
return true;
10591062
}
10601063

1064+
const bool exit_after_use = PTSettingsHelper::is_low_memory_mode_enabled(get_key());
1065+
m_process_manager.start(exit_after_use);
1066+
10611067
if (hotkeyId == 1)
10621068
{ // m_advanced_paste_ui_hotkey
10631069
Logger::trace(L"Setting start up event");

0 commit comments

Comments
 (0)