Compare commits

...

1 Commits

Author SHA1 Message Date
Gordon Lam (SH)
4511a2024e feat(advancedpaste): add auto-copy selection for custom action hotkeys 2026-01-16 22:25:25 +08:00
5 changed files with 144 additions and 2 deletions

View File

@@ -60,6 +60,7 @@ namespace
const wchar_t JSON_KEY_IS_AI_ENABLED[] = L"IsAIEnabled";
const wchar_t JSON_KEY_IS_OPEN_AI_ENABLED[] = L"IsOpenAIEnabled";
const wchar_t JSON_KEY_SHOW_CUSTOM_PREVIEW[] = L"ShowCustomPreview";
const wchar_t JSON_KEY_AUTO_COPY_SELECTION_CUSTOM_ACTION[] = L"AutoCopySelectionForCustomActionHotkey";
const wchar_t JSON_KEY_PASTE_AI_CONFIGURATION[] = L"paste-ai-configuration";
const wchar_t JSON_KEY_PROVIDERS[] = L"providers";
const wchar_t JSON_KEY_SERVICE_TYPE[] = L"service-type";
@@ -102,6 +103,7 @@ private:
bool m_is_ai_enabled = false;
bool m_is_advanced_ai_enabled = false;
bool m_preview_custom_format_output = true;
bool m_auto_copy_selection_custom_action = false;
// Event listening for external triggers (e.g., from CmdPal extension)
EventWaiter m_triggerEventWaiter;
@@ -348,6 +350,11 @@ private:
{
m_preview_custom_format_output = propertiesObject.GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
}
if (propertiesObject.HasKey(JSON_KEY_AUTO_COPY_SELECTION_CUSTOM_ACTION))
{
m_auto_copy_selection_custom_action = propertiesObject.GetNamedObject(JSON_KEY_AUTO_COPY_SELECTION_CUSTOM_ACTION).GetNamedBoolean(JSON_KEY_VALUE, false);
}
}
if (old_data_migrated)
@@ -481,6 +488,88 @@ private:
}
}
bool try_send_copy_message()
{
GUITHREADINFO gui_info = {};
gui_info.cbSize = sizeof(GUITHREADINFO);
if (!GetGUIThreadInfo(0, &gui_info))
{
return false;
}
HWND target = gui_info.hwndFocus ? gui_info.hwndFocus : gui_info.hwndActive;
if (!target)
{
return false;
}
DWORD_PTR result = 0;
return SendMessageTimeout(target,
WM_COPY,
0,
0,
SMTO_ABORTIFHUNG | SMTO_BLOCK,
100,
&result) != 0;
}
void send_copy_selection()
{
if (try_send_copy_message())
{
return;
}
std::vector<INPUT> inputs;
// send Ctrl+C (key downs and key ups)
{
INPUT input_event = {};
input_event.type = INPUT_KEYBOARD;
input_event.ki.wVk = VK_CONTROL;
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
inputs.push_back(input_event);
}
{
INPUT input_event = {};
input_event.type = INPUT_KEYBOARD;
input_event.ki.wVk = 0x43; // C
// Avoid triggering detection by the centralized keyboard hook.
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
inputs.push_back(input_event);
}
{
INPUT input_event = {};
input_event.type = INPUT_KEYBOARD;
input_event.ki.wVk = 0x43; // C
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
// Avoid triggering detection by the centralized keyboard hook.
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
inputs.push_back(input_event);
}
{
INPUT input_event = {};
input_event.type = INPUT_KEYBOARD;
input_event.ki.wVk = VK_CONTROL;
input_event.ki.dwFlags = KEYEVENTF_KEYUP;
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
inputs.push_back(input_event);
}
auto uSent = SendInput(static_cast<UINT>(inputs.size()), inputs.data(), sizeof(INPUT));
if (uSent != inputs.size())
{
DWORD errorCode = GetLastError();
auto errorMessage = get_last_error_message(errorCode);
Logger::error(L"SendInput failed for Ctrl+C. Expected to send {} inputs and sent only {}. {}", inputs.size(), uSent, errorMessage.has_value() ? errorMessage.value() : L"");
Trace::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput");
}
}
void try_to_paste_as_plain_text()
{
std::wstring clipboard_text;
@@ -826,6 +915,25 @@ public:
Logger::trace(L"AdvancedPaste hotkey pressed");
if (m_enabled)
{
size_t additional_action_index = 0;
size_t custom_action_index = 0;
bool is_custom_action_hotkey = false;
if (hotkeyId >= NUM_DEFAULT_HOTKEYS)
{
additional_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
if (additional_action_index >= m_additional_actions.size())
{
custom_action_index = additional_action_index - m_additional_actions.size();
is_custom_action_hotkey = custom_action_index < m_custom_actions.size();
}
}
if (is_custom_action_hotkey && m_auto_copy_selection_custom_action)
{
send_copy_selection();
}
m_process_manager.start();
// hotkeyId in same order as set by get_hotkeys
@@ -868,7 +976,6 @@ public:
}
const auto additional_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
if (additional_action_index < m_additional_actions.size())
{
const auto& id = m_additional_actions.at(additional_action_index).id;
@@ -881,7 +988,6 @@ public:
return true;
}
const auto custom_action_index = additional_action_index - m_additional_actions.size();
if (custom_action_index < m_custom_actions.size())
{
const auto id = m_custom_actions.at(custom_action_index).id;

View File

@@ -28,6 +28,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
ShowCustomPreview = true;
CloseAfterLosingFocus = false;
EnableClipboardPreview = true;
AutoCopySelectionForCustomActionHotkey = false;
PasteAIConfiguration = new();
}
@@ -79,6 +80,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool EnableClipboardPreview { get; set; }
[JsonConverter(typeof(BoolPropertyJsonConverter))]
public bool AutoCopySelectionForCustomActionHotkey { get; set; }
[JsonPropertyName("advanced-paste-ui-hotkey")]
public HotkeySettings AdvancedPasteUIShortcut { get; set; }

View File

@@ -171,6 +171,11 @@
<tkcontrols:SettingsCard Name="AdvancedPasteEnableClipboardPreview" ContentAlignment="Left">
<controls:CheckBoxWithDescriptionControl x:Uid="AdvancedPaste_EnableClipboardPreview" IsChecked="{x:Bind ViewModel.EnableClipboardPreview, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard Name="AdvancedPasteAutoCopySelectionCustomAction" ContentAlignment="Left">
<controls:CheckBoxWithDescriptionControl
x:Uid="AdvancedPaste_AutoCopySelectionForCustomActionHotkey"
IsChecked="{x:Bind ViewModel.AutoCopySelectionForCustomActionHotkey, Mode=TwoWay}" />
</tkcontrols:SettingsCard>
<tkcontrols:SettingsCard
Name="AdvancedPasteClipboardHistoryEnabledSettingsCard"
ContentAlignment="Left"

View File

@@ -4631,6 +4631,14 @@ Activate by holding the key for the character you want to add an accent to, then
<value>Show clipboard preview</value>
<comment>Enables display of clipboard contents preview in the Advanced Paste window</comment>
</data>
<data name="AdvancedPaste_AutoCopySelectionForCustomActionHotkey.Header" xml:space="preserve">
<value>Auto-copy selection for custom action hotkeys</value>
<comment>Advanced Paste is a product name</comment>
</data>
<data name="AdvancedPaste_AutoCopySelectionForCustomActionHotkey.Description" xml:space="preserve">
<value>Boost productivity with a single shortcut that copies the selection and runs Advanced Paste</value>
<comment>Advanced Paste is a product name</comment>
</data>
<data name="GPO_CommandNotFound_ForceDisabled.Title" xml:space="preserve">
<value>The Command Not Found module is disabled by your organization.</value>
<comment>"Command Not Found" is a product name</comment>

View File

@@ -555,6 +555,19 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public bool AutoCopySelectionForCustomActionHotkey
{
get => _advancedPasteSettings.Properties.AutoCopySelectionForCustomActionHotkey;
set
{
if (value != _advancedPasteSettings.Properties.AutoCopySelectionForCustomActionHotkey)
{
_advancedPasteSettings.Properties.AutoCopySelectionForCustomActionHotkey = value;
NotifySettingsChanged();
}
}
}
public bool IsConflictingCopyShortcut =>
_customActions.Select(customAction => customAction.Shortcut)
.Concat([PasteAsPlainTextShortcut, AdvancedPasteUIShortcut, PasteAsMarkdownShortcut, PasteAsJsonShortcut])
@@ -1219,6 +1232,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
OnPropertyChanged(nameof(EnableClipboardPreview));
}
if (target.AutoCopySelectionForCustomActionHotkey != source.AutoCopySelectionForCustomActionHotkey)
{
target.AutoCopySelectionForCustomActionHotkey = source.AutoCopySelectionForCustomActionHotkey;
OnPropertyChanged(nameof(AutoCopySelectionForCustomActionHotkey));
}
var incomingConfig = source.PasteAIConfiguration ?? new PasteAIConfiguration();
if (ShouldReplacePasteAIConfiguration(target.PasteAIConfiguration, incomingConfig))
{