[AdvancedPaste]Additional actions - Image to text, Paste as file (txt, png, html) (#35167)

* [AdvancedPaste] Additional actions, including Image to text

* Spellcheck issue

* [AdvancedPaste] Paste as file and many other improvements

* Fixed typo

* Fixed typo

* [AdvancedPaste] Improved paste window menu layout

* [AdvancedPaste] Improved settings window layout

* [AdvancedPaste] Removed AudioToText for the moment

* Code cleanup

* Minor fixes

* Changed log-line with potentially sensitive info

* Extra telemetry for AdvancedPaste

* Added 'Hotkey' suffix to AdvancedPaste_Settings telemetry event
This commit is contained in:
Ani
2024-10-18 15:34:09 +02:00
committed by GitHub
parent 14139affc5
commit dd5cd2a570
41 changed files with 1435 additions and 624 deletions

View File

@@ -42,6 +42,7 @@ namespace
{
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
const wchar_t JSON_KEY_CUSTOM_ACTIONS[] = L"custom-actions";
const wchar_t JSON_KEY_ADDITIONAL_ACTIONS[] = L"additional-actions";
const wchar_t JSON_KEY_SHORTCUT[] = L"shortcut";
const wchar_t JSON_KEY_IS_SHOWN[] = L"isShown";
const wchar_t JSON_KEY_ID[] = L"id";
@@ -73,7 +74,6 @@ private:
HANDLE m_hProcess;
std::thread create_pipe_thread;
std::unique_ptr<CAtlFile> m_write_pipe;
// Time to wait for process to close after sending WM_CLOSE signal
@@ -86,8 +86,18 @@ private:
Hotkey m_paste_as_markdown_hotkey{};
Hotkey m_paste_as_json_hotkey{};
std::vector<Hotkey> m_custom_action_hotkeys;
std::vector<int> m_custom_action_ids;
template<class Id>
struct ActionData
{
Id id;
Hotkey hotkey;
};
using AdditionalAction = ActionData<std::wstring>;
std::vector<AdditionalAction> m_additional_actions;
using CustomAction = ActionData<int>;
std::vector<CustomAction> m_custom_actions;
bool m_preview_custom_format_output = true;
@@ -166,6 +176,34 @@ private:
open_ai_key_exists();
}
static std::wstring kebab_to_pascal_case(const std::wstring& kebab_str)
{
std::wstring result;
bool capitalize_next = true;
for (const auto ch : kebab_str)
{
if (ch == L'-')
{
capitalize_next = true;
}
else
{
if (capitalize_next)
{
result += std::towupper(ch);
capitalize_next = false;
}
else
{
result += ch;
}
}
}
return result;
}
bool migrate_data_and_remove_data_file(Hotkey& old_paste_as_plain_hotkey)
{
const wchar_t OLD_JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
@@ -197,6 +235,39 @@ private:
return false;
}
void process_additional_action(const winrt::hstring& actionName, const winrt::Windows::Data::Json::IJsonValue& actionValue)
{
if (actionValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
return;
}
const auto action = actionValue.GetObjectW();
if (!action.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
return;
}
if (action.HasKey(JSON_KEY_SHORTCUT))
{
const AdditionalAction additionalAction
{
actionName.c_str(),
parse_single_hotkey(action.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_additional_actions.push_back(additionalAction);
}
else
{
for (const auto& [subActionName, subAction] : action)
{
process_additional_action(subActionName, subAction);
}
}
}
void parse_hotkeys(PowerToysSettings::PowerToyValues& settings)
{
auto settingsObject = settings.get_raw_json();
@@ -239,13 +310,23 @@ private:
*hotkey = parse_single_hotkey(keyName, settingsObject);
}
m_custom_action_hotkeys.clear();
m_custom_action_ids.clear();
m_additional_actions.clear();
m_custom_actions.clear();
if (settingsObject.HasKey(JSON_KEY_PROPERTIES))
{
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
if (propertiesObject.HasKey(JSON_KEY_ADDITIONAL_ACTIONS))
{
const auto additionalActions = propertiesObject.GetNamedObject(JSON_KEY_ADDITIONAL_ACTIONS);
for (const auto& [actionName, additionalAction] : additionalActions)
{
process_additional_action(actionName, additionalAction);
}
}
if (propertiesObject.HasKey(JSON_KEY_CUSTOM_ACTIONS))
{
const auto customActions = propertiesObject.GetNamedObject(JSON_KEY_CUSTOM_ACTIONS).GetNamedArray(JSON_KEY_VALUE);
@@ -257,8 +338,13 @@ private:
if (object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false))
{
m_custom_action_hotkeys.push_back(parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT)));
m_custom_action_ids.push_back(static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)));
const CustomAction customActionData
{
static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)),
parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT))
};
m_custom_actions.push_back(customActionData);
}
}
}
@@ -331,7 +417,7 @@ private:
return;
}
create_pipe_thread = std::thread([&] { start_named_pipe_server(pipe_name.value()); });
std::thread create_pipe_thread ([&]{ start_named_pipe_server(pipe_name.value()); });
launch_process(pipe_name.value());
create_pipe_thread.join();
}
@@ -730,12 +816,19 @@ public:
m_preview_custom_format_output = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
}
std::unordered_map<std::wstring, Hotkey> additionalActionMap;
for (const auto& action : m_additional_actions)
{
additionalActionMap[kebab_to_pascal_case(action.id)] = action.hotkey;
}
// order of args matter
Trace::AdvancedPaste_SettingsTelemetry(m_paste_as_plain_hotkey,
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey,
m_preview_custom_format_output);
m_preview_custom_format_output,
additionalActionMap);
// If you don't need to do any custom processing of the settings, proceed
// to persists the values calling:
@@ -825,11 +918,24 @@ public:
return true;
}
const auto custom_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
if (custom_action_index < m_custom_action_ids.size())
const auto additional_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
if (additional_action_index < m_additional_actions.size())
{
const auto id = m_custom_action_ids.at(custom_action_index);
const auto& id = m_additional_actions.at(additional_action_index).id;
Logger::trace(L"Starting additional action id={}", id);
Trace::AdvancedPaste_Invoked(std::format(L"{}Direct", kebab_to_pascal_case(id)));
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_ADDITIONAL_ACTION_MESSAGE, id);
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;
Logger::trace(L"Starting custom action id={}", id);
@@ -844,7 +950,7 @@ public:
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
const size_t num_hotkeys = NUM_DEFAULT_HOTKEYS + m_custom_action_hotkeys.size();
const size_t num_hotkeys = NUM_DEFAULT_HOTKEYS + m_additional_actions.size() + m_custom_actions.size();
if (hotkeys && buffer_size >= num_hotkeys)
{
@@ -852,9 +958,11 @@ public:
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey };
std::copy(default_hotkeys.begin(), default_hotkeys.end(), hotkeys);
std::copy(m_custom_action_hotkeys.begin(), m_custom_action_hotkeys.end(), hotkeys + NUM_DEFAULT_HOTKEYS);
const auto get_action_hotkey = [](const auto& action) { return action.hotkey; };
std::transform(m_additional_actions.begin(), m_additional_actions.end(), hotkeys + NUM_DEFAULT_HOTKEYS, get_action_hotkey);
std::transform(m_custom_actions.begin(), m_custom_actions.end(), hotkeys + NUM_DEFAULT_HOTKEYS + m_additional_actions.size(), get_action_hotkey);
}
return num_hotkeys;