mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-27 14:59:16 +08:00
[KBM]Launch apps / URI with keyboard shortcuts, support chords (#30121)
* Working UI update with just runProgram Path and isRunProgram * First working, basic. no args or path, or setting change detections. * Revert and fixed. * Some clean up, working with config file monitor * Args and Start-in should be working. * File monitor, quotes, xaml screens one * Fixed enable/disable toogle from XAML * Code cleanup. * Betting logging. * Cleanup, start of RunProgramDescriptor and usage of run_non_elevated/run_elevated * Code moved to KeyboardEventHandlers, but not enabled since it won't build as is, needs elevation.h. Other testing.. * Key chords working, pretty much * Added gui for elevation level, need to refresh on change... * f: include shellapi.h and reference wil in KBMEL * run_elevated/run_non_elevated sorted out. Working! * Removed lots of old temp code. * Fix some speling errors. * Cleanup before trying to add a UI for the chord * Added "DifferentUser" option * Closer on UI for chords. * Better UI, lots working. * Clean up * Text for “Allow chords” – needs to look better… * Bugs and clean-up * Cleanup * Refactor and clean up. * More clean up * Some localization. * Don’t show “Allow chords“ to the “to” shortcut * Maybe better foreground after opening new app * Better chord matching. * Runprogram fix for stealing existing shortcut. * Better runProgram stuff * Temp commit * Working well * Toast test * More toast * Added File and Folder picker UI * Pre-check on run program file exists. * Refactor to SetupRunProgramControls * Open URI UI is going. * Open URI working well * Open URI stuff working well * Allowed AppSpecific shortcut and fixed backup/restore shortcut dups * Fixed settings screen * Start of code to find by name... * UI fixed * Small fixes * Some single edit code working. * UI getting better. * Fixes * Fixed and merge from main * UI updates * UI updates. * UI stuff * Fixed crash from move ui item locations. * Fixed crash from move ui item locations. * Added delete confirm * Basic sound working. * Localized some stuff * Added sounds * Better experiance when shortcut is in use. * UI tweaks * Fixed KBM ui for unicode shortcut not having "," * Some clean up * Cleanup * Cleanup * Fixed applyXamlStyling * Added back stuff lost in merge * applyXamlStyling, again * Fixed crash on change from non shortcut to shortcut * Update src/modules/keyboardmanager/KeyboardManagerEngineTest/KeyboardManagerEngineTest.vcxproj * Fixed some spelling type issues. * ImplementationLibrary 231216 * Comment bump to see if the Microsoft.Windows.ImplementationLibrary version thing gets picked up * Correct, Microsoft.Windows.ImplementationLibrary, finally? * Fixed two test that failed because we now allow key-chords. * Removed shortcut sounds. * use original behavior when "allow chords" is off in shortcut window * fix crash when editing a shortcut that has apps specified for it * split KBM chords with comma on dashboard page * Fix some spelling items. * More "spelling" * Fix XAML styling * align TextBlock and ToggleSwitch * fix cutoff issue at the top * increase ComboBox width * Added *Unsupported* for backwards compat on config of KBM * fix spellcheck * Fix crash on Remap key screen * Fixed Remap Keys ComboBox width too short. * Removed KBM Single Edit mode, fixed crash. * Fix Xaml with xaml cops * Fix crash on setting "target app" for some types of shortcuts. * Space to toggle chord, combobox back * fix spellcheck * fix some code nits * Code review updates. * Add exclusions to the bug report tool * Code review and kill CloseAndEndTask * Fix alignment / 3 comboboxes per row * Fix daily telemetry events to exclude start app and open URI * Add chords and remove app start and open uri from config telemetry * comma instead of plus in human readable shortcut telemetry data * Code review, restore default-old state when new row added in KBM * Code review, restore default-old state when new row added in KBM, part 2 * Still show target app on Settings * Only allow enabling chords for origin shortcuts --------- Co-authored-by: Andrey Nekrasov <yuyoyuppe@users.noreply.github.com> Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
This commit is contained in:
parent
1a5349bf1e
commit
725c8e8c19
1
.github/actions/spell-check/allow/code.txt
vendored
1
.github/actions/spell-check/allow/code.txt
vendored
@ -84,6 +84,7 @@ Ctrls
|
||||
EXSEL
|
||||
HOLDENTER
|
||||
HOLDESC
|
||||
HOLDSPACE
|
||||
KBDLLHOOKSTRUCT
|
||||
keyevent
|
||||
LAlt
|
||||
|
2
.github/actions/spell-check/allow/names.txt
vendored
2
.github/actions/spell-check/allow/names.txt
vendored
@ -99,6 +99,7 @@ Pooja
|
||||
Quriz
|
||||
randyrants
|
||||
ricardosantos
|
||||
ritchielawrence
|
||||
robmikh
|
||||
Rutkas
|
||||
ryanbodrug
|
||||
@ -128,6 +129,7 @@ Zykova
|
||||
|
||||
# OTHERS
|
||||
|
||||
cmdow
|
||||
Controlz
|
||||
cortana
|
||||
fancymouse
|
||||
|
16
.github/actions/spell-check/expect.txt
vendored
16
.github/actions/spell-check/expect.txt
vendored
@ -99,6 +99,7 @@ bbwe
|
||||
bck
|
||||
BESTEFFORT
|
||||
bhid
|
||||
BIF
|
||||
bigbar
|
||||
bigobj
|
||||
binlog
|
||||
@ -125,6 +126,7 @@ BPBF
|
||||
bpmf
|
||||
bpp
|
||||
Browsable
|
||||
BROWSEINFO
|
||||
bsd
|
||||
bthprops
|
||||
bti
|
||||
@ -212,6 +214,7 @@ cominterop
|
||||
commandline
|
||||
COMMANDTITLE
|
||||
commctrl
|
||||
commdlg
|
||||
compmgmt
|
||||
COMPOSITIONFULL
|
||||
comsupp
|
||||
@ -473,6 +476,7 @@ FILELOCKSMITHCONTEXTMENU
|
||||
FILELOCKSMITHEXT
|
||||
FILELOCKSMITHLIB
|
||||
FILELOCKSMITHLIBINTEROP
|
||||
FILEMUSTEXIST
|
||||
FILEOP
|
||||
FILEOS
|
||||
FILESUBTYPE
|
||||
@ -801,6 +805,7 @@ LPCTSTR
|
||||
lpdw
|
||||
lpfn
|
||||
LPINPUT
|
||||
LPITEMIDLIST
|
||||
lpmi
|
||||
LPMINMAXINFO
|
||||
LPMONITORINFO
|
||||
@ -808,6 +813,7 @@ LPOSVERSIONINFOEXW
|
||||
LPQUERY
|
||||
lprc
|
||||
LPSAFEARRAY
|
||||
lpstr
|
||||
lpsz
|
||||
lpt
|
||||
LPTHREAD
|
||||
@ -971,6 +977,7 @@ netsetup
|
||||
netsh
|
||||
newcolor
|
||||
newdev
|
||||
NEWDIALOGSTYLE
|
||||
newitem
|
||||
newpath
|
||||
newrow
|
||||
@ -1041,6 +1048,7 @@ ocr
|
||||
Ocrsettings
|
||||
odbccp
|
||||
officehubintl
|
||||
OFN
|
||||
ofs
|
||||
oldcolor
|
||||
olditem
|
||||
@ -1051,6 +1059,7 @@ OLECHAR
|
||||
onebranch
|
||||
OOBEPT
|
||||
opencode
|
||||
OPENFILENAME
|
||||
opensource
|
||||
openxmlformats
|
||||
OPTIMIZEFORINVOKE
|
||||
@ -1085,6 +1094,7 @@ parray
|
||||
PARTIALCONFIRMATIONDIALOGTITLE
|
||||
PATCOPY
|
||||
pathcch
|
||||
PATHMUSTEXIST
|
||||
Pathto
|
||||
PATINVERT
|
||||
PATPAINT
|
||||
@ -1173,6 +1183,7 @@ PRINTCLIENT
|
||||
printmanagement
|
||||
prm
|
||||
proactively
|
||||
PROCESSENTRY
|
||||
PROCESSKEY
|
||||
processthreadsapi
|
||||
PRODEXT
|
||||
@ -1292,6 +1303,7 @@ RESTORETOMAXIMIZED
|
||||
restrictedcapabilities
|
||||
restrictederrorinfo
|
||||
resultlist
|
||||
RETURNONLYFSDIRS
|
||||
RGBQUAD
|
||||
rgbs
|
||||
rgelt
|
||||
@ -1436,6 +1448,7 @@ sln
|
||||
SMALLICON
|
||||
smartphone
|
||||
SMTO
|
||||
SNAPPROCESS
|
||||
snwprintf
|
||||
softline
|
||||
SOURCECLIENTAREAONLY
|
||||
@ -1578,6 +1591,7 @@ tlbimp
|
||||
TMPVAR
|
||||
TNP
|
||||
toggleswitch
|
||||
Toolhelp
|
||||
toolkitconverters
|
||||
Toolset
|
||||
toolwindow
|
||||
@ -1634,6 +1648,7 @@ Updatelayout
|
||||
UPGRADINGPRODUCTCODE
|
||||
Uptool
|
||||
urld
|
||||
urlmon
|
||||
Usb
|
||||
USEDEFAULT
|
||||
USEFILEATTRIBUTES
|
||||
@ -1709,6 +1724,7 @@ wcautil
|
||||
WCE
|
||||
wcex
|
||||
WClass
|
||||
wcsicmp
|
||||
wcsnicmp
|
||||
WDA
|
||||
wdp
|
||||
|
@ -27,6 +27,17 @@ std::wstring LayoutMap::GetKeyName(DWORD key)
|
||||
return impl->GetKeyName(key);
|
||||
}
|
||||
|
||||
DWORD LayoutMap::GetKeyFromName(const std::wstring& name)
|
||||
{
|
||||
auto list = impl->GetKeyNameList(false);
|
||||
for (const auto& [value, key] : list)
|
||||
{
|
||||
if (key == name)
|
||||
return value;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<DWORD> LayoutMap::GetKeyCodeList(const bool isShortcut)
|
||||
{
|
||||
return impl->GetKeyCodeList(isShortcut);
|
||||
|
@ -11,6 +11,7 @@ public:
|
||||
~LayoutMap();
|
||||
void UpdateLayout();
|
||||
std::wstring GetKeyName(DWORD key);
|
||||
DWORD GetKeyFromName(const std::wstring& name);
|
||||
std::vector<DWORD> GetKeyCodeList(const bool isShortcut = false);
|
||||
std::vector<std::pair<DWORD, std::wstring>> GetKeyNameList(const bool isShortcut = false);
|
||||
|
||||
|
@ -71,7 +71,7 @@ namespace
|
||||
}
|
||||
|
||||
result = spView->QueryInterface(riid, ppv);
|
||||
if (result != S_OK || ppv == nullptr || *ppv == nullptr )
|
||||
if (result != S_OK || ppv == nullptr || *ppv == nullptr)
|
||||
{
|
||||
Logger::warn(L"Failed to query interface. {}", GetErrorString(result));
|
||||
return false;
|
||||
@ -83,7 +83,7 @@ namespace
|
||||
inline bool GetDesktopAutomationObject(REFIID riid, void** ppv)
|
||||
{
|
||||
CComPtr<IShellView> spsv;
|
||||
|
||||
|
||||
// Desktop may not be available on startup
|
||||
auto attempts = 5;
|
||||
for (auto i = 1; i <= attempts; i++)
|
||||
@ -207,8 +207,34 @@ inline bool drop_elevated_privileges()
|
||||
return result;
|
||||
}
|
||||
|
||||
// Run command as different user, returns true if succeeded
|
||||
inline HANDLE run_as_different_user(const std::wstring& file, const std::wstring& params, const wchar_t* workingDir = nullptr, const bool showWindow = true)
|
||||
{
|
||||
Logger::info(L"run_elevated with params={}", params);
|
||||
SHELLEXECUTEINFOW exec_info = { 0 };
|
||||
exec_info.cbSize = sizeof(SHELLEXECUTEINFOW);
|
||||
exec_info.lpVerb = L"runAsUser";
|
||||
exec_info.lpFile = file.c_str();
|
||||
exec_info.lpParameters = params.c_str();
|
||||
exec_info.hwnd = 0;
|
||||
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
exec_info.lpDirectory = workingDir;
|
||||
exec_info.hInstApp = 0;
|
||||
if (showWindow)
|
||||
{
|
||||
exec_info.nShow = SW_SHOWDEFAULT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// might have limited success, but only option with ShellExecuteExW
|
||||
exec_info.nShow = SW_HIDE;
|
||||
}
|
||||
|
||||
return ShellExecuteExW(&exec_info) ? exec_info.hProcess : nullptr;
|
||||
}
|
||||
|
||||
// Run command as elevated user, returns true if succeeded
|
||||
inline HANDLE run_elevated(const std::wstring& file, const std::wstring& params)
|
||||
inline HANDLE run_elevated(const std::wstring& file, const std::wstring& params, const wchar_t* workingDir = nullptr, const bool showWindow = true)
|
||||
{
|
||||
Logger::info(L"run_elevated with params={}", params);
|
||||
SHELLEXECUTEINFOW exec_info = { 0 };
|
||||
@ -218,15 +244,24 @@ inline HANDLE run_elevated(const std::wstring& file, const std::wstring& params)
|
||||
exec_info.lpParameters = params.c_str();
|
||||
exec_info.hwnd = 0;
|
||||
exec_info.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
exec_info.lpDirectory = 0;
|
||||
exec_info.lpDirectory = workingDir;
|
||||
exec_info.hInstApp = 0;
|
||||
exec_info.nShow = SW_SHOWDEFAULT;
|
||||
|
||||
if (showWindow)
|
||||
{
|
||||
exec_info.nShow = SW_SHOWDEFAULT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// might have limited success, but only option with ShellExecuteExW
|
||||
exec_info.nShow = SW_HIDE;
|
||||
}
|
||||
|
||||
return ShellExecuteExW(&exec_info) ? exec_info.hProcess : nullptr;
|
||||
}
|
||||
|
||||
// Run command as non-elevated user, returns true if succeeded, puts the process id into returnPid if returnPid != NULL
|
||||
inline bool run_non_elevated(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir = nullptr)
|
||||
inline bool run_non_elevated(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir = nullptr, const bool showWindow = true)
|
||||
{
|
||||
Logger::info(L"run_non_elevated with params={}", params);
|
||||
auto executable_args = L"\"" + file + L"\"";
|
||||
@ -292,15 +327,22 @@ inline bool run_non_elevated(const std::wstring& file, const std::wstring& param
|
||||
STARTUPINFOEX siex = { 0 };
|
||||
siex.lpAttributeList = pptal;
|
||||
siex.StartupInfo.cb = sizeof(siex);
|
||||
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
auto dwCreationFlags = EXTENDED_STARTUPINFO_PRESENT;
|
||||
|
||||
if (!showWindow)
|
||||
{
|
||||
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
|
||||
siex.StartupInfo.wShowWindow = SW_HIDE;
|
||||
dwCreationFlags = CREATE_NO_WINDOW;
|
||||
}
|
||||
|
||||
auto succeeded = CreateProcessW(file.c_str(),
|
||||
&executable_args[0],
|
||||
nullptr,
|
||||
nullptr,
|
||||
FALSE,
|
||||
EXTENDED_STARTUPINFO_PRESENT,
|
||||
dwCreationFlags,
|
||||
nullptr,
|
||||
workingDir,
|
||||
&siex.StartupInfo,
|
||||
@ -341,7 +383,10 @@ inline bool RunNonElevatedEx(const std::wstring& file, const std::wstring& param
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
if (SUCCEEDED(co_init)) CoUninitialize();
|
||||
if (SUCCEEDED(co_init))
|
||||
{
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
@ -372,7 +417,7 @@ inline std::optional<ProcessInfo> RunNonElevatedFailsafe(const std::wstring& fil
|
||||
}
|
||||
}
|
||||
|
||||
auto handles = getProcessHandlesByName(std::filesystem::path{ file }.filename().wstring(), PROCESS_QUERY_INFORMATION | SYNCHRONIZE | handleAccess );
|
||||
auto handles = getProcessHandlesByName(std::filesystem::path{ file }.filename().wstring(), PROCESS_QUERY_INFORMATION | SYNCHRONIZE | handleAccess);
|
||||
|
||||
if (handles.empty())
|
||||
return std::nullopt;
|
||||
@ -385,7 +430,7 @@ inline std::optional<ProcessInfo> RunNonElevatedFailsafe(const std::wstring& fil
|
||||
}
|
||||
|
||||
// Run command with the same elevation, returns true if succeeded
|
||||
inline bool run_same_elevation(const std::wstring& file, const std::wstring& params, DWORD* returnPid)
|
||||
inline bool run_same_elevation(const std::wstring& file, const std::wstring& params, DWORD* returnPid, const wchar_t* workingDir = nullptr)
|
||||
{
|
||||
auto executable_args = L"\"" + file + L"\"";
|
||||
if (!params.empty())
|
||||
@ -403,7 +448,7 @@ inline bool run_same_elevation(const std::wstring& file, const std::wstring& par
|
||||
FALSE,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr,
|
||||
workingDir,
|
||||
&si,
|
||||
&pi);
|
||||
|
||||
|
@ -77,8 +77,26 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
|
||||
type = static_cast<KeyboardManagerEditorType>(_wtoi(cmdArgs[1]));
|
||||
}
|
||||
|
||||
if (numArgs == 3)
|
||||
std::wstring keysForShortcutToEdit = L"";
|
||||
std::wstring action = L"";
|
||||
|
||||
|
||||
// do some parsing of the cmdline arg to see if we need to behave different
|
||||
// like, single edit mode, or "delete" mode.
|
||||
// These extra args are from "OpenEditor" in the KeyboardManagerViewModel
|
||||
if (numArgs >= 3)
|
||||
{
|
||||
if (numArgs >= 4)
|
||||
{
|
||||
keysForShortcutToEdit = std::wstring(cmdArgs[3]);
|
||||
}
|
||||
|
||||
if (numArgs >= 5)
|
||||
{
|
||||
action = std::wstring(cmdArgs[4]);
|
||||
}
|
||||
|
||||
|
||||
std::wstring pid = std::wstring(cmdArgs[2]);
|
||||
Logger::trace(L"Editor started from the settings with pid {}", pid);
|
||||
if (!pid.empty())
|
||||
@ -108,7 +126,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
|
||||
return -1;
|
||||
}
|
||||
|
||||
editor->OpenEditorWindow(type);
|
||||
editor->OpenEditorWindow(type, keysForShortcutToEdit, action);
|
||||
|
||||
editor = nullptr;
|
||||
|
||||
@ -149,7 +167,7 @@ bool KeyboardManagerEditor::StartLowLevelKeyboardHook()
|
||||
return (hook != nullptr);
|
||||
}
|
||||
|
||||
void KeyboardManagerEditor::OpenEditorWindow(KeyboardManagerEditorType type)
|
||||
void KeyboardManagerEditor::OpenEditorWindow(KeyboardManagerEditorType type, std::wstring keysForShortcutToEdit, std::wstring action)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
@ -157,7 +175,7 @@ void KeyboardManagerEditor::OpenEditorWindow(KeyboardManagerEditorType type)
|
||||
CreateEditKeyboardWindow(hInstance, keyboardManagerState, mappingConfiguration);
|
||||
break;
|
||||
case KeyboardManagerEditorType::ShortcutEditor:
|
||||
CreateEditShortcutsWindow(hInstance, keyboardManagerState, mappingConfiguration);
|
||||
CreateEditShortcutsWindow(hInstance, keyboardManagerState, mappingConfiguration, keysForShortcutToEdit, action);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,20 +23,20 @@ public:
|
||||
}
|
||||
|
||||
bool StartLowLevelKeyboardHook();
|
||||
void OpenEditorWindow(KeyboardManagerEditorType type);
|
||||
void OpenEditorWindow(KeyboardManagerEditorType type, std::wstring keysForShortcutToEdit, std::wstring action);
|
||||
|
||||
// Function called by the hook procedure to handle the events. This is the starting point function for remapping
|
||||
intptr_t HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept;
|
||||
|
||||
private:
|
||||
static LRESULT CALLBACK KeyHookProc(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
|
||||
inline static HHOOK hook;
|
||||
HINSTANCE hInstance;
|
||||
|
||||
KBMEditor::KeyboardManagerState keyboardManagerState;
|
||||
MappingConfiguration mappingConfiguration;
|
||||
|
||||
|
||||
// Object of class which implements InputInterface. Required for calling library functions while enabling testing
|
||||
KeyboardManagerInput::Input inputHandler;
|
||||
};
|
||||
|
@ -154,7 +154,9 @@
|
||||
<Manifest Include="KeyboardManagerEditor.exe.manifest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources.resx" />
|
||||
<None Include="Resources.resx">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="Keyboard.ico" />
|
||||
|
@ -144,6 +144,15 @@
|
||||
<data name="EditShortcuts_WindowName" xml:space="preserve">
|
||||
<value>Remap shortcuts</value>
|
||||
</data>
|
||||
<data name="Edit_This_Shortcut_WindowName" xml:space="preserve">
|
||||
<value>Edit shortcut</value>
|
||||
</data>
|
||||
<data name="Browse_For_Program_Button" xml:space="preserve">
|
||||
<value>Select program</value>
|
||||
</data>
|
||||
<data name="Browse_For_Path_Button" xml:space="preserve">
|
||||
<value>Select path</value>
|
||||
</data>
|
||||
<data name="Ok_Button" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
@ -161,12 +170,60 @@
|
||||
<value>To send:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_TargetHeader" xml:space="preserve">
|
||||
<value>To send:</value>
|
||||
<value>To:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_TargetAppHeader" xml:space="preserve">
|
||||
<value>Target app:</value>
|
||||
</data>
|
||||
<data name="EditKeyboard_OrphanedDialogTitle" xml:space="preserve">
|
||||
<data name="EditShortcuts_Label_Args" xml:space="preserve">
|
||||
<value>Args:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_Elevation" xml:space="preserve">
|
||||
<value>Elevation:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_Start_As" xml:space="preserve">
|
||||
<value>Visibility:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_Program" xml:space="preserve">
|
||||
<value>App:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_Start_In" xml:space="preserve">
|
||||
<value>Start in:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_Action" xml:space="preserve">
|
||||
<value>Action:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_Path_URI" xml:space="preserve">
|
||||
<value>Path/URI:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_Shortcut" xml:space="preserve">
|
||||
<value>Shortcut:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_Keys" xml:space="preserve">
|
||||
<value>Keys:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Visibility_Normal" xml:space="preserve">
|
||||
<value>Normal</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Visibility_Hidden" xml:space="preserve">
|
||||
<value>Hidden</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_If_Running" xml:space="preserve">
|
||||
<value>If running:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Label_App_Running_Sound" xml:space="preserve">
|
||||
<value>Running sound:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_LabelApp_Not_Running_Sound" xml:space="preserve">
|
||||
<value>Start sound:</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Bad_Key" xml:space="preserve">
|
||||
<value>Key in use</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_BtnSelectFile" xml:space="preserve">
|
||||
<value>Select file</value>
|
||||
</data>
|
||||
<data name="EditKeyboard_OrphanedDialogTitle" xml:space="preserve">
|
||||
<value>Warning: The following keys do not have assignments:</value>
|
||||
<comment>Key on a keyboard</comment>
|
||||
</data>
|
||||
@ -184,7 +241,11 @@
|
||||
<comment>Key on a keyboard</comment>
|
||||
</data>
|
||||
<data name="EditShortcuts_Info" xml:space="preserve">
|
||||
<value>Select the shortcut you want to change ("Select") and then configure the key, shortcut or text you want it to send ("To send").</value>
|
||||
<value>Edit shortcuts to activate and then configure the actions to happen when activated.</value>
|
||||
<comment>Key on a keyboard</comment>
|
||||
</data>
|
||||
<data name="Edit_Single_Shortcut_Info" xml:space="preserve">
|
||||
<value>Edit shortcut to activate and then configure the action to happen when activated.</value>
|
||||
<comment>Key on a keyboard</comment>
|
||||
</data>
|
||||
<data name="EditShortcuts_InfoExample" xml:space="preserve">
|
||||
@ -211,6 +272,9 @@
|
||||
<value>Keys selected:</value>
|
||||
<comment>Keys on a keyboard</comment>
|
||||
</data>
|
||||
<data name="Type_HoldSpace" xml:space="preserve">
|
||||
<value>Hold Space to toggle allow chord</value>
|
||||
</data>
|
||||
<data name="Type_HoldEnter" xml:space="preserve">
|
||||
<value>Hold Enter to continue</value>
|
||||
</data>
|
||||
@ -220,6 +284,24 @@
|
||||
<data name="EditShortcuts_AllApps" xml:space="preserve">
|
||||
<value>All apps</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Allow_Chords" xml:space="preserve">
|
||||
<value>Allow chords</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Path_To_Program" xml:space="preserve">
|
||||
<value>Path to program</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Uri_Example" xml:space="preserve">
|
||||
<value>E.g.: https://website.url</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_What_Can_I_Use_Link" xml:space="preserve">
|
||||
<value>What can I use?</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Args_For_Program" xml:space="preserve">
|
||||
<value>Arguments for program</value>
|
||||
</data>
|
||||
<data name="EditShortcuts_Start_In_Dir_For_Program" xml:space="preserve">
|
||||
<value>Start in directory</value>
|
||||
</data>
|
||||
<data name="ErrorMessage_RemapSuccessful" xml:space="preserve">
|
||||
<value>Remapping successful</value>
|
||||
</data>
|
||||
@ -288,12 +370,20 @@
|
||||
<comment>Key on a keyboard</comment>
|
||||
</data>
|
||||
<data name="Mapping_Type_DropDown_Text" xml:space="preserve">
|
||||
<value>Text</value>
|
||||
<value>Send Text</value>
|
||||
</data>
|
||||
<data name="Mapping_Type_DropDown_Key_Shortcut" xml:space="preserve">
|
||||
<value>Key/Shortcut</value>
|
||||
<value>Send Key/Shortcut</value>
|
||||
<comment>Key on a keyboard</comment>
|
||||
</data>
|
||||
<data name="Mapping_Type_DropDown_Run_Program" xml:space="preserve">
|
||||
<value>Run Program</value>
|
||||
<comment>Run Program</comment>
|
||||
</data>
|
||||
<data name="Mapping_Type_DropDown_Open_URI" xml:space="preserve">
|
||||
<value>Open URI</value>
|
||||
<comment>URI to open.</comment>
|
||||
</data>
|
||||
<data name="Add_Key_Remap_Button" xml:space="preserve">
|
||||
<value>Add key remapping</value>
|
||||
<comment>Key on a keyboard</comment>
|
||||
@ -304,6 +394,33 @@
|
||||
<data name="Delete_Remapping_Button" xml:space="preserve">
|
||||
<value>Delete remapping</value>
|
||||
</data>
|
||||
<data name="Already_Running_Do_Nothing" xml:space="preserve">
|
||||
<value>Do nothing</value>
|
||||
</data>
|
||||
<data name="Already_Running_Show_Window" xml:space="preserve">
|
||||
<value>Show window</value>
|
||||
</data>
|
||||
<data name="Already_Running_Start_Another" xml:space="preserve">
|
||||
<value>Start another</value>
|
||||
</data>
|
||||
<data name="Already_Running_Close" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
</data>
|
||||
<data name="Already_Running_Terminate" xml:space="preserve">
|
||||
<value>End task</value>
|
||||
</data>
|
||||
<data name="Already_Running_Close_And_Terminate" xml:space="preserve">
|
||||
<value>Close and end task</value>
|
||||
</data>
|
||||
<data name="Elevation_Type_Normal" xml:space="preserve">
|
||||
<value>Normal</value>
|
||||
</data>
|
||||
<data name="Elevation_Type_Elevated" xml:space="preserve">
|
||||
<value>Elevated</value>
|
||||
</data>
|
||||
<data name="Elevation_Type_Different_User" xml:space="preserve">
|
||||
<value>Different User</value>
|
||||
</data>
|
||||
<data name="Delete_Remapping_Event" xml:space="preserve">
|
||||
<value>Remapping deleted</value>
|
||||
</data>
|
||||
|
@ -204,8 +204,9 @@ namespace BufferValidationHelpers
|
||||
}
|
||||
else
|
||||
{
|
||||
// warn and reset the drop down
|
||||
errorType = ShortcutErrorType::ShortcutNotMoreThanOneActionKey;
|
||||
// this used to "warn and reset the drop down" but for now, since we will allow Chords, we do allow this
|
||||
// leaving the here and commented out for posterity, for now.
|
||||
// errorType = ShortcutErrorType::ShortcutNotMoreThanOneActionKey;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -69,7 +69,7 @@ static IAsyncAction OnClickAccept(
|
||||
}
|
||||
|
||||
// Function to create the Edit Shortcuts Window
|
||||
inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration)
|
||||
inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration, std::wstring keysForShortcutToEdit, std::wstring action)
|
||||
{
|
||||
Logger::trace("CreateEditShortcutsWindowImpl()");
|
||||
auto locker = EventLocker::Get(KeyboardManagerConstants::EditorWindowEventName.c_str());
|
||||
@ -107,12 +107,24 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
isEditShortcutsWindowRegistrationCompleted = true;
|
||||
}
|
||||
|
||||
// we might be passed via cmdline some keysForShortcutToEdit, this means we're editing just one shortcut
|
||||
if (!keysForShortcutToEdit.empty())
|
||||
{
|
||||
}
|
||||
|
||||
// Find coordinates of the screen where the settings window is placed.
|
||||
RECT desktopRect = UIHelpers::GetForegroundWindowDesktopRect();
|
||||
|
||||
// Calculate DPI dependent window size
|
||||
float windowWidth = EditorConstants::DefaultEditShortcutsWindowWidth;
|
||||
float windowHeight = EditorConstants::DefaultEditShortcutsWindowHeight;
|
||||
|
||||
if (!keysForShortcutToEdit.empty())
|
||||
{
|
||||
windowWidth = EditorConstants::DefaultEditSingleShortcutsWindowWidth;
|
||||
windowHeight = EditorConstants::DefaultEditSingleShortcutsWindowHeight;
|
||||
}
|
||||
|
||||
DPIAware::ConvertByCursorPosition(windowWidth, windowHeight);
|
||||
DPIAware::GetScreenDPIForCursor(g_currentDPI);
|
||||
|
||||
@ -129,13 +141,13 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
NULL,
|
||||
hInst,
|
||||
NULL);
|
||||
|
||||
|
||||
if (_hWndEditShortcutsWindow == NULL)
|
||||
{
|
||||
MessageBox(NULL, GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORMESSAGE).c_str(), GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORTITLE).c_str(), NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Ensures the window is in foreground on first startup. If this is not done, the window appears behind because the thread is not on the foreground.
|
||||
if (_hWndEditShortcutsWindow)
|
||||
{
|
||||
@ -157,7 +169,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
|
||||
// Create the xaml bridge object
|
||||
XamlBridge2 xamlBridge(_hWndEditShortcutsWindow);
|
||||
|
||||
|
||||
// Create the desktop window xaml source object and set its content
|
||||
hWndXamlIslandEditShortcutsWindow = xamlBridge.InitBridge();
|
||||
|
||||
@ -227,15 +239,15 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
|
||||
// Store handle of edit shortcuts window
|
||||
ShortcutControl::editShortcutsWindowHandle = _hWndEditShortcutsWindow;
|
||||
|
||||
|
||||
// Store keyboard manager state
|
||||
ShortcutControl::keyboardManagerState = &keyboardManagerState;
|
||||
KeyDropDownControl::keyboardManagerState = &keyboardManagerState;
|
||||
KeyDropDownControl::mappingConfiguration = &mappingConfiguration;
|
||||
|
||||
|
||||
// Clear the shortcut remap buffer
|
||||
ShortcutControl::shortcutRemapBuffer.clear();
|
||||
|
||||
|
||||
// Vector to store dynamically allocated control objects to avoid early destruction
|
||||
std::vector<std::vector<std::unique_ptr<ShortcutControl>>> keyboardRemapControlObjects;
|
||||
|
||||
@ -246,27 +258,9 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
// Create copy of the remaps to avoid concurrent access
|
||||
ShortcutRemapTable osLevelShortcutReMapCopy = mappingConfiguration.osLevelShortcutReMap;
|
||||
|
||||
for (const auto& it : osLevelShortcutReMapCopy)
|
||||
{
|
||||
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, it.first, it.second.targetShortcut);
|
||||
}
|
||||
|
||||
// Load existing app-specific shortcuts into UI
|
||||
// Create copy of the remaps to avoid concurrent access
|
||||
AppSpecificShortcutRemapTable appSpecificShortcutReMapCopy = mappingConfiguration.appSpecificShortcutReMap;
|
||||
|
||||
// Iterate through all the apps
|
||||
for (const auto& itApp : appSpecificShortcutReMapCopy)
|
||||
{
|
||||
// Iterate through shortcuts for each app
|
||||
for (const auto& itShortcut : itApp.second)
|
||||
{
|
||||
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, itShortcut.first, itShortcut.second.targetShortcut, itApp.first);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply button
|
||||
Button applyButton;
|
||||
applyButton.Name(L"applyButton");
|
||||
applyButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_OK_BUTTON)));
|
||||
applyButton.Style(AccentButtonStyle());
|
||||
applyButton.MinWidth(EditorConstants::HeaderButtonWidth);
|
||||
@ -284,7 +278,44 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
OnClickAccept(keyboardManagerState, applyButton.XamlRoot(), ApplyRemappings);
|
||||
});
|
||||
|
||||
auto OnClickAcceptNoCheckFn = ApplyRemappings;
|
||||
|
||||
for (const auto& it : osLevelShortcutReMapCopy)
|
||||
{
|
||||
auto isHidden = false;
|
||||
|
||||
// check to see if this should be hidden because it's NOT the one we are looking for.
|
||||
// It will still be there for backward compatability, just not visible
|
||||
if (!keysForShortcutToEdit.empty())
|
||||
{
|
||||
isHidden = (keysForShortcutToEdit != it.first.ToHstringVK());
|
||||
}
|
||||
|
||||
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, it.first, it.second.targetShortcut, L"");
|
||||
}
|
||||
|
||||
// Load existing app-specific shortcuts into UI
|
||||
// Create copy of the remaps to avoid concurrent access
|
||||
AppSpecificShortcutRemapTable appSpecificShortcutReMapCopy = mappingConfiguration.appSpecificShortcutReMap;
|
||||
|
||||
// Iterate through all the apps
|
||||
for (const auto& itApp : appSpecificShortcutReMapCopy)
|
||||
{
|
||||
// Iterate through shortcuts for each app
|
||||
for (const auto& itShortcut : itApp.second)
|
||||
{
|
||||
auto isHidden = false;
|
||||
if (!keysForShortcutToEdit.empty())
|
||||
{
|
||||
isHidden = (keysForShortcutToEdit != itShortcut.first.ToHstringVK());
|
||||
}
|
||||
|
||||
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, itShortcut.first, itShortcut.second.targetShortcut, itApp.first);
|
||||
}
|
||||
}
|
||||
|
||||
header.Children().Append(headerText);
|
||||
|
||||
header.Children().Append(applyButton);
|
||||
header.Children().Append(cancelButton);
|
||||
|
||||
@ -299,17 +330,41 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
addShortcut.Margin({ 10, 10, 0, 25 });
|
||||
addShortcut.Style(AccentButtonStyle());
|
||||
addShortcut.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects);
|
||||
ShortcutControl& newShortcut = ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects);
|
||||
|
||||
// Whenever a remap is added move to the bottom of the screen
|
||||
scrollViewer.ChangeView(nullptr, scrollViewer.ScrollableHeight(), nullptr);
|
||||
|
||||
// Set focus to the first Type Button in the newly added row
|
||||
UIHelpers::SetFocusOnTypeButtonInLastRow(shortcutTable, EditorConstants::ShortcutTableColCount);
|
||||
|
||||
//newShortcut.OpenNewShortcutControlRow(shortcutTable, shortcutTable.Children().GetAt(shortcutTable.Children().Size() - 1).as<StackPanel>());
|
||||
});
|
||||
|
||||
// if this is a delete action we just want to quick load the screen to delete the shortcut and close
|
||||
// this is so we can delete from the KBM settings screen
|
||||
if (action == L"isDelete")
|
||||
{
|
||||
auto indexToDelete = -1;
|
||||
for (int i = 0; i < ShortcutControl::shortcutRemapBuffer.size(); i++)
|
||||
{
|
||||
auto tempShortcut = std::get<Shortcut>(ShortcutControl::shortcutRemapBuffer[i].first[0]);
|
||||
if (tempShortcut.ToHstringVK() == keysForShortcutToEdit)
|
||||
{
|
||||
indexToDelete = i;
|
||||
}
|
||||
}
|
||||
if (indexToDelete >= 0)
|
||||
{
|
||||
ShortcutControl::shortcutRemapBuffer.erase(ShortcutControl::shortcutRemapBuffer.begin() + indexToDelete);
|
||||
}
|
||||
OnClickAcceptNoCheckFn();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remap shortcut button content
|
||||
StackPanel addShortcutContent;
|
||||
|
||||
addShortcutContent.Orientation(Orientation::Horizontal);
|
||||
addShortcutContent.Spacing(10);
|
||||
addShortcutContent.Children().Append(SymbolIcon(Symbol::Add));
|
||||
@ -333,6 +388,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
mappingsPanel.Children().Append(addShortcut);
|
||||
|
||||
// Remapping table should be scrollable
|
||||
|
||||
scrollViewer.Content(mappingsPanel);
|
||||
|
||||
RelativePanel xamlContainer;
|
||||
@ -347,6 +403,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
xamlContainer.Children().Append(header);
|
||||
xamlContainer.Children().Append(helperText);
|
||||
xamlContainer.Children().Append(scrollViewer);
|
||||
|
||||
try
|
||||
{
|
||||
// If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
|
||||
@ -368,6 +425,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
// Mica isn't available
|
||||
xamlContainer.Background(Application::Current().Resources().Lookup(box_value(L"ApplicationPageBackgroundThemeBrush")).as<Media::SolidColorBrush>());
|
||||
}
|
||||
|
||||
Window::Current().Content(xamlContent);
|
||||
|
||||
////End XAML Island section
|
||||
@ -389,10 +447,10 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa
|
||||
keyboardManagerState.ClearRegisteredKeyDelays();
|
||||
}
|
||||
|
||||
void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration)
|
||||
void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration, std::wstring keysForShortcutToEdit, std::wstring action)
|
||||
{
|
||||
// Move implementation into the separate method so resources get destroyed correctly
|
||||
CreateEditShortcutsWindowImpl(hInst, keyboardManagerState, mappingConfiguration);
|
||||
CreateEditShortcutsWindowImpl(hInst, keyboardManagerState, mappingConfiguration, keysForShortcutToEdit, action);
|
||||
|
||||
// Calling ClearXamlIslands() outside of the message loop is not enough to prevent
|
||||
// Microsoft.UI.XAML.dll from crashing during deinitialization, see https://github.com/microsoft/PowerToys/issues/10906
|
||||
@ -420,7 +478,9 @@ LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wPa
|
||||
LPMINMAXINFO mmi = reinterpret_cast<LPMINMAXINFO>(lParam);
|
||||
float minWidth = EditorConstants::MinimumEditShortcutsWindowWidth;
|
||||
float minHeight = EditorConstants::MinimumEditShortcutsWindowHeight;
|
||||
|
||||
DPIAware::Convert(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL), minWidth, minHeight);
|
||||
|
||||
mmi->ptMinTrackSize.x = static_cast<LONG>(minWidth);
|
||||
mmi->ptMinTrackSize.y = static_cast<LONG>(minHeight);
|
||||
}
|
||||
@ -453,8 +513,7 @@ LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wPa
|
||||
rect->top,
|
||||
rect->right - rect->left,
|
||||
rect->bottom - rect->top,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE
|
||||
);
|
||||
SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
|
||||
Logger::trace(L"WM_DPICHANGED: new dpi {} rect {} {} ", newDPI, rect->right - rect->left, rect->bottom - rect->top);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace KBMEditor
|
||||
class MappingConfiguration;
|
||||
|
||||
// Function to create the Edit Shortcuts Window
|
||||
void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration);
|
||||
void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration, std::wstring keysForShortcutToEdit, std::wstring action);
|
||||
|
||||
// Function to check if there is already a window active if yes bring to foreground
|
||||
bool CheckEditShortcutsWindowActive();
|
||||
|
@ -10,8 +10,13 @@ namespace EditorConstants
|
||||
inline const int EditKeyboardTableMinWidth = 700;
|
||||
inline const int DefaultEditShortcutsWindowWidth = 1410;
|
||||
inline const int DefaultEditShortcutsWindowHeight = 600;
|
||||
inline const int DefaultEditSingleShortcutsWindowWidth = 1080;
|
||||
inline const int DefaultEditSingleShortcutsWindowHeight = 400;
|
||||
inline const int MinimumEditShortcutsWindowWidth = 500;
|
||||
inline const int MinimumEditShortcutsWindowHeight = 500;
|
||||
inline const int MinimumEditSingleShortcutsWindowWidth = 500;
|
||||
inline const int MinimumEditSingleShortcutsWindowHeight = 600;
|
||||
|
||||
inline const int EditShortcutsTableMinWidth = 1000;
|
||||
|
||||
// Key Remap table constants
|
||||
|
@ -52,6 +52,16 @@ namespace EditorHelpers
|
||||
// Function to return true if the shortcut is valid. A valid shortcut has atleast one modifier, as well as an action key
|
||||
bool IsValidShortcut(Shortcut shortcut)
|
||||
{
|
||||
if (shortcut.operationType == Shortcut::OperationType::RunProgram && shortcut.runProgramFilePath.length() > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shortcut.operationType == Shortcut::OperationType::OpenURI && shortcut.uriToOpen.length() > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shortcut.actionKey != NULL)
|
||||
{
|
||||
if (shortcut.winKey != ModifierKey::Disabled || shortcut.ctrlKey != ModifierKey::Disabled || shortcut.altKey != ModifierKey::Disabled || shortcut.shiftKey != ModifierKey::Disabled)
|
||||
|
@ -30,6 +30,11 @@ DWORD KeyDropDownControl::GetSelectedValue(ComboBox comboBox)
|
||||
return stoul(std::wstring(value));
|
||||
}
|
||||
|
||||
DWORD KeyDropDownControl::GetSelectedValue(TextBlock text)
|
||||
{
|
||||
return keyboardManagerState->keyboardMap.GetKeyFromName(std::wstring{ text.Text() });
|
||||
}
|
||||
|
||||
void KeyDropDownControl::SetSelectedValue(std::wstring value)
|
||||
{
|
||||
this->dropDown.as<ComboBox>().SelectedValue(winrt::box_value(value));
|
||||
@ -87,7 +92,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl
|
||||
auto child0 = Media::VisualTreeHelper::GetChild(combo, 0);
|
||||
if (!child0)
|
||||
return;
|
||||
|
||||
|
||||
auto grid = child0.as<Grid>();
|
||||
if (!grid)
|
||||
return;
|
||||
@ -95,7 +100,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl
|
||||
auto& gridChildren = grid.Children();
|
||||
if (!gridChildren)
|
||||
return;
|
||||
|
||||
|
||||
gridChildren.Append(warningTip);
|
||||
});
|
||||
|
||||
@ -287,10 +292,8 @@ void KeyDropDownControl::SetSelectionHandler(StackPanel& table, StackPanel row,
|
||||
}
|
||||
else
|
||||
{
|
||||
Shortcut tempShortcut;
|
||||
tempShortcut.SetKeyCodes(selectedKeyCodes);
|
||||
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
|
||||
shortcutRemapBuffer[validationResult.second].first[colIndex] = tempShortcut;
|
||||
shortcutRemapBuffer[validationResult.second].first[colIndex] = Shortcut(selectedKeyCodes);
|
||||
}
|
||||
}
|
||||
|
||||
@ -364,10 +367,31 @@ std::vector<int32_t> KeyDropDownControl::GetSelectedCodesFromStackPanel(Variable
|
||||
std::vector<int32_t> selectedKeyCodes;
|
||||
|
||||
// Get selected indices for each drop down
|
||||
for (int i = 0; i < (int)parent.Children().Size(); i++)
|
||||
for (uint32_t i = 0; i < parent.Children().Size(); i++)
|
||||
{
|
||||
ComboBox ItDropDown = parent.Children().GetAt(i).as<ComboBox>();
|
||||
selectedKeyCodes.push_back(GetSelectedValue(ItDropDown));
|
||||
if (auto ItDropDown = parent.Children().GetAt(i).try_as<ComboBox>(); ItDropDown)
|
||||
{
|
||||
selectedKeyCodes.push_back(GetSelectedValue(ItDropDown));
|
||||
}
|
||||
|
||||
// If it's a ShortcutControl -> use its layout, see KeyboardManagerState::AddKeyToLayout
|
||||
else if (auto sp = parent.Children().GetAt(i).try_as<StackPanel>(); sp)
|
||||
{
|
||||
for (uint32_t j = 0; j < sp.Children().Size(); ++j)
|
||||
{
|
||||
auto border = sp.Children().GetAt(j).try_as<Border>();
|
||||
|
||||
// if this is null then this is a different layout
|
||||
// likely because this not a shortcut to another shortcut but rather
|
||||
// run app or open uri
|
||||
|
||||
if (border != nullptr)
|
||||
{
|
||||
auto textBlock = border.Child().try_as<TextBlock>();
|
||||
selectedKeyCodes.push_back(GetSelectedValue(textBlock));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return selectedKeyCodes;
|
||||
@ -432,27 +456,36 @@ void KeyDropDownControl::AddShortcutToControl(Shortcut shortcut, StackPanel tabl
|
||||
// Remove references to the old drop down objects to destroy them
|
||||
keyDropDownControlObjects.clear();
|
||||
std::vector<DWORD> shortcutKeyCodes = shortcut.GetKeyCodes();
|
||||
if (shortcutKeyCodes.size() != 0)
|
||||
|
||||
auto secondKey = shortcut.GetSecondKey();
|
||||
|
||||
bool ignoreWarning = false;
|
||||
|
||||
// If more than one key is to be added, ignore a shortcut to key warning on partially entering the remapping
|
||||
if (shortcutKeyCodes.size() > 1)
|
||||
{
|
||||
bool ignoreWarning = false;
|
||||
ignoreWarning = true;
|
||||
}
|
||||
|
||||
// If more than one key is to be added, ignore a shortcut to key warning on partially entering the remapping
|
||||
if (shortcutKeyCodes.size() > 1)
|
||||
KeyDropDownControl::AddDropDown(table, row, parent, colIndex, remapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow, ignoreWarning);
|
||||
|
||||
for (int i = 0; i < shortcutKeyCodes.size(); i++)
|
||||
{
|
||||
// New drop down gets added automatically when the SelectedValue(key code) is set
|
||||
if (i < (int)parent.Children().Size())
|
||||
{
|
||||
ignoreWarning = true;
|
||||
ComboBox currentDropDown = parent.Children().GetAt(i).as<ComboBox>();
|
||||
currentDropDown.SelectedValue(winrt::box_value(std::to_wstring(shortcutKeyCodes[i])));
|
||||
}
|
||||
}
|
||||
|
||||
if (shortcut.HasChord())
|
||||
{
|
||||
// if this has a chord, render it last
|
||||
KeyDropDownControl::AddDropDown(table, row, parent, colIndex, remapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, isSingleKeyWindow, ignoreWarning);
|
||||
|
||||
for (int i = 0; i < shortcutKeyCodes.size(); i++)
|
||||
{
|
||||
// New drop down gets added automatically when the SelectedValue(key code) is set
|
||||
if (i < (int)parent.Children().Size())
|
||||
{
|
||||
ComboBox currentDropDown = parent.Children().GetAt(i).as<ComboBox>();
|
||||
currentDropDown.SelectedValue(winrt::box_value(std::to_wstring(shortcutKeyCodes[i])));
|
||||
}
|
||||
}
|
||||
auto nextI = static_cast<int>(shortcutKeyCodes.size());
|
||||
ComboBox currentDropDown = parent.Children().GetAt(nextI).as<ComboBox>();
|
||||
currentDropDown.SelectedValue(winrt::box_value(std::to_wstring(shortcut.GetSecondKey())));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@ private:
|
||||
|
||||
// Get selected value of dropdown or -1 if nothing is selected
|
||||
static DWORD GetSelectedValue(ComboBox comboBox);
|
||||
static DWORD GetSelectedValue(TextBlock text);
|
||||
|
||||
// Function to set accessible name for combobox
|
||||
static void SetAccessibleNameForComboBox(ComboBox dropDown, int index);
|
||||
@ -110,7 +111,7 @@ public:
|
||||
static void AddShortcutToControl(Shortcut shortcut, StackPanel table, VariableSizedWrapGrid parent, KBMEditor::KeyboardManagerState& keyboardManagerState, const int colIndex, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, RemapBuffer& remapBuffer, StackPanel row, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow);
|
||||
|
||||
// Get keys name list depending if Disable is in dropdown
|
||||
static std::vector<std::pair<DWORD,std::wstring>> GetKeyList(bool isShortcut, bool renderDisable);
|
||||
static std::vector<std::pair<DWORD, std::wstring>> GetKeyList(bool isShortcut, bool renderDisable);
|
||||
|
||||
// Get number of selected keys. Do not count -1 and 0 values as they stand for Not selected and None
|
||||
static int GetNumberOfSelectedKeys(std::vector<int32_t> keys);
|
||||
|
@ -22,6 +22,16 @@ namespace KeyboardManagerEditorStrings
|
||||
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_KEY_SHORTCUT);
|
||||
}
|
||||
|
||||
inline std::wstring MappingTypeRunProgram()
|
||||
{
|
||||
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_RUN_PROGRAM);
|
||||
}
|
||||
|
||||
inline std::wstring MappingTypeOpenUri()
|
||||
{
|
||||
return GET_RESOURCE_STRING(IDS_MAPPING_TYPE_DROPDOWN_OPEN_URI);
|
||||
}
|
||||
|
||||
// Function to return the error message
|
||||
winrt::hstring GetErrorMessage(ShortcutErrorType errorType);
|
||||
}
|
||||
|
@ -110,13 +110,13 @@ void KeyboardManagerState::ConfigureDetectSingleKeyRemapUI(const StackPanel& tex
|
||||
currentSingleKeyUI = textBlock.as<winrt::Windows::Foundation::IInspectable>();
|
||||
}
|
||||
|
||||
void KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring& key)
|
||||
TextBlock KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring& key)
|
||||
{
|
||||
// Textblock to display the detected key
|
||||
TextBlock remapKey;
|
||||
Border border;
|
||||
|
||||
border.Padding({ 20, 10, 20, 10 });
|
||||
border.Padding({ 10, 5, 10, 5 });
|
||||
border.Margin({ 0, 0, 10, 0 });
|
||||
|
||||
// Based on settings-ui\Settings.UI\SettingsXAML\Controls\KeyVisual\KeyVisual.xaml
|
||||
@ -127,12 +127,15 @@ void KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring
|
||||
remapKey.Foreground(Application::Current().Resources().Lookup(box_value(L"ButtonForeground")).as<Media::Brush>());
|
||||
remapKey.FontWeight(Text::FontWeights::SemiBold());
|
||||
|
||||
remapKey.FontSize(20);
|
||||
remapKey.FontSize(14);
|
||||
|
||||
border.HorizontalAlignment(HorizontalAlignment::Left);
|
||||
border.Child(remapKey);
|
||||
|
||||
remapKey.Text(key);
|
||||
panel.Children().Append(border);
|
||||
|
||||
return remapKey;
|
||||
}
|
||||
|
||||
// Function to update the detect shortcut UI based on the entered keys
|
||||
@ -167,17 +170,39 @@ void KeyboardManagerState::UpdateDetectShortcutUI()
|
||||
currentShortcutUI2.as<StackPanel>().Visibility(Visibility::Collapsed);
|
||||
}
|
||||
|
||||
auto lastStackPanel = currentShortcutUI2.as<StackPanel>();
|
||||
for (int i = 0; i < shortcut.size(); i++)
|
||||
{
|
||||
if (i < 3)
|
||||
{
|
||||
AddKeyToLayout(currentShortcutUI1.as<StackPanel>(), shortcut[i]);
|
||||
lastStackPanel = currentShortcutUI1.as<StackPanel>();
|
||||
}
|
||||
else
|
||||
{
|
||||
AddKeyToLayout(currentShortcutUI2.as<StackPanel>(), shortcut[i]);
|
||||
lastStackPanel = currentShortcutUI2.as<StackPanel>();
|
||||
}
|
||||
}
|
||||
|
||||
if (!AllowChord)
|
||||
{
|
||||
detectedShortcut.secondKey = NULL;
|
||||
}
|
||||
|
||||
// add a TextBlock, to show what shortcut in text, e.g.: "CTRL+j, k" OR "CTRL+j, CTRL+k".
|
||||
if (detectedShortcut.HasChord())
|
||||
{
|
||||
TextBlock txtComma;
|
||||
txtComma.Text(L",");
|
||||
txtComma.FontSize(20);
|
||||
txtComma.Padding({ 0, 0, 10, 0 });
|
||||
txtComma.VerticalAlignment(VerticalAlignment::Bottom);
|
||||
txtComma.TextAlignment(TextAlignment::Left);
|
||||
lastStackPanel.Children().Append(txtComma);
|
||||
AddKeyToLayout(lastStackPanel, EditorHelpers::GetKeyVector(Shortcut(detectedShortcutCopy.secondKey), keyboardMap)[0]);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
|
||||
@ -228,6 +253,12 @@ Shortcut KeyboardManagerState::GetDetectedShortcut()
|
||||
return currentShortcut;
|
||||
}
|
||||
|
||||
void KeyboardManagerState::SetDetectedShortcut(Shortcut shortcut)
|
||||
{
|
||||
detectedShortcut = shortcut;
|
||||
UpdateDetectShortcutUI();
|
||||
}
|
||||
|
||||
// Function to return the currently detected remap key which is displayed on the UI
|
||||
DWORD KeyboardManagerState::GetDetectedSingleRemapKey()
|
||||
{
|
||||
@ -246,9 +277,57 @@ void KeyboardManagerState::SelectDetectedRemapKey(DWORD key)
|
||||
void KeyboardManagerState::SelectDetectedShortcut(DWORD key)
|
||||
{
|
||||
// Set the new key and store if a change occurred
|
||||
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
|
||||
bool updateUI = detectedShortcut.SetKey(key);
|
||||
lock.unlock();
|
||||
bool updateUI = false;
|
||||
|
||||
if (AllowChord)
|
||||
{
|
||||
// Code to determine if we're building/updating a chord.
|
||||
auto currentFirstKey = detectedShortcut.GetActionKey();
|
||||
auto currentSecondKey = detectedShortcut.GetSecondKey();
|
||||
|
||||
Shortcut tempShortcut = Shortcut(key);
|
||||
bool isKeyActionTypeKey = (tempShortcut.actionKey != NULL);
|
||||
|
||||
if (isKeyActionTypeKey)
|
||||
{
|
||||
// we want a chord and already have the first key set
|
||||
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
|
||||
|
||||
if (currentFirstKey == NULL)
|
||||
{
|
||||
Logger::trace(L"AllowChord AND no first");
|
||||
updateUI = detectedShortcut.SetKey(key);
|
||||
}
|
||||
else if (currentSecondKey == NULL)
|
||||
{
|
||||
// we don't have the second key, set it now
|
||||
Logger::trace(L"AllowChord AND we have first key of {}, will use {}", currentFirstKey, key);
|
||||
updateUI = detectedShortcut.SetSecondKey(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
// we already have the second key, swap it to first, and use new as second
|
||||
Logger::trace(L"DO have secondKey, will make first {} and second {}", currentSecondKey, key);
|
||||
detectedShortcut.actionKey = currentSecondKey;
|
||||
detectedShortcut.secondKey = key;
|
||||
updateUI = true;
|
||||
}
|
||||
updateUI = true;
|
||||
lock.unlock();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
|
||||
updateUI = detectedShortcut.SetKey(key);
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(detectedShortcut_mutex);
|
||||
updateUI = detectedShortcut.SetKey(key);
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
if (updateUI)
|
||||
{
|
||||
@ -261,9 +340,12 @@ void KeyboardManagerState::SelectDetectedShortcut(DWORD key)
|
||||
void KeyboardManagerState::ResetDetectedShortcutKey(DWORD key)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(detectedShortcut_mutex);
|
||||
detectedShortcut.ResetKey(key);
|
||||
// only clear if mod, not if action, since we need to keek actionKey and secondKey for chord
|
||||
if (Shortcut::IsModifier(key))
|
||||
{
|
||||
detectedShortcut.ResetKey(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Function which can be used in HandleKeyboardHookEvent before the single key remap event to use the UI and suppress events while the remap window is active.
|
||||
Helpers::KeyboardHookDecision KeyboardManagerState::DetectSingleRemapKeyUIBackend(LowlevelKeyboardEvent* data)
|
||||
{
|
||||
@ -372,6 +454,12 @@ void KeyboardManagerState::ClearRegisteredKeyDelays()
|
||||
keyDelays.clear();
|
||||
}
|
||||
|
||||
void KBMEditor::KeyboardManagerState::ClearStoredShortcut()
|
||||
{
|
||||
std::scoped_lock<std::mutex> detectedShortcut_lock(detectedShortcut_mutex);
|
||||
detectedShortcut.Reset();
|
||||
}
|
||||
|
||||
bool KeyboardManagerState::HandleKeyDelayEvent(LowlevelKeyboardEvent* ev)
|
||||
{
|
||||
if (currentUIWindow != GetForegroundWindow())
|
||||
|
@ -22,6 +22,7 @@ namespace Helpers
|
||||
namespace winrt::Windows::UI::Xaml::Controls
|
||||
{
|
||||
struct StackPanel;
|
||||
struct TextBlock;
|
||||
}
|
||||
|
||||
namespace KBMEditor
|
||||
@ -80,10 +81,15 @@ namespace KBMEditor
|
||||
std::map<DWORD, std::unique_ptr<KeyDelay>> keyDelays;
|
||||
std::mutex keyDelays_mutex;
|
||||
|
||||
// Display a key by appending a border Control as a child of the panel.
|
||||
void AddKeyToLayout(const winrt::Windows::UI::Xaml::Controls::StackPanel& panel, const winrt::hstring& key);
|
||||
|
||||
public:
|
||||
// Display a key by appending a border Control as a child of the panel.
|
||||
winrt::Windows::UI::Xaml::Controls::TextBlock AddKeyToLayout(const winrt::Windows::UI::Xaml::Controls::StackPanel& panel, const winrt::hstring& key);
|
||||
|
||||
|
||||
|
||||
// flag to set if we want to allow building a chord
|
||||
bool AllowChord = false;
|
||||
|
||||
// Stores the keyboard layout
|
||||
LayoutMap keyboardMap;
|
||||
|
||||
@ -120,6 +126,9 @@ namespace KBMEditor
|
||||
// Function to return the currently detected shortcut which is displayed on the UI
|
||||
Shortcut GetDetectedShortcut();
|
||||
|
||||
// Function to SetDetectedShortcut and also UpdateDetectShortcutUI
|
||||
void KeyboardManagerState::SetDetectedShortcut(Shortcut shortcut);
|
||||
|
||||
// Function to return the currently detected remap key which is displayed on the UI
|
||||
DWORD GetDetectedSingleRemapKey();
|
||||
|
||||
@ -146,6 +155,8 @@ namespace KBMEditor
|
||||
// Function to clear all the registered key delays
|
||||
void ClearRegisteredKeyDelays();
|
||||
|
||||
void ClearStoredShortcut();
|
||||
|
||||
// Handle a key event, for a delayed key.
|
||||
bool HandleKeyDelayEvent(LowlevelKeyboardEvent* ev);
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "pch.h"
|
||||
#include "ShortcutControl.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <commdlg.h>
|
||||
#include <ShlObj.h>
|
||||
#include <common/interop/shared_constants.h>
|
||||
|
||||
#include "KeyboardManagerState.h"
|
||||
@ -19,8 +21,9 @@ RemapBuffer ShortcutControl::shortcutRemapBuffer;
|
||||
ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int colIndex, TextBox targetApp)
|
||||
{
|
||||
shortcutDropDownVariableSizedWrapGrid = VariableSizedWrapGrid();
|
||||
typeShortcut = Button();
|
||||
btnPickShortcut = Button();
|
||||
shortcutControlLayout = StackPanel();
|
||||
|
||||
const bool isHybridControl = colIndex == 1;
|
||||
|
||||
// TODO: Check if there is a VariableSizedWrapGrid equivalent.
|
||||
@ -28,28 +31,44 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col
|
||||
shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
|
||||
shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>().MaximumRowsOrColumns(3);
|
||||
|
||||
typeShortcut.as<Button>().Content(winrt::box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
|
||||
typeShortcut.as<Button>().Width(EditorConstants::ShortcutTableDropDownWidth);
|
||||
typeShortcut.as<Button>().Click([&, table, row, colIndex, isHybridControl, targetApp](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
btnPickShortcut.as<Button>().Content(winrt::box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
|
||||
btnPickShortcut.as<Button>().Width(EditorConstants::ShortcutTableDropDownWidth / 2);
|
||||
btnPickShortcut.as<Button>().Click([&, table, row, colIndex, isHybridControl, targetApp](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
keyboardManagerState->SetUIState(KBMEditor::KeyboardManagerUIState::DetectShortcutWindowActivated, editShortcutsWindowHandle);
|
||||
// Using the XamlRoot of the typeShortcut to get the root of the XAML host
|
||||
CreateDetectShortcutWindow(sender, sender.as<Button>().XamlRoot(), *keyboardManagerState, colIndex, table, keyDropDownControlObjects, row, targetApp, isHybridControl, false, editShortcutsWindowHandle, shortcutRemapBuffer);
|
||||
});
|
||||
|
||||
FontIcon fontIcon;
|
||||
fontIcon.Glyph(L"\uE70F"); // Unicode for the accept icon
|
||||
fontIcon.FontFamily(Media::FontFamily(L"Segoe MDL2 Assets")); // Set the font family to Segoe MDL2 Assets
|
||||
// Set the FontIcon as the content of the button
|
||||
btnPickShortcut.as<Button>().Content(fontIcon);
|
||||
|
||||
uint32_t rowIndex;
|
||||
|
||||
UIElementCollection children = table.Children();
|
||||
bool indexFound = children.IndexOf(row, rowIndex);
|
||||
|
||||
auto nameX = L"btnPickShortcut_" + std::to_wstring(colIndex);
|
||||
btnPickShortcut.as<Button>().Name(nameX);
|
||||
|
||||
// Set an accessible name for the type shortcut button
|
||||
typeShortcut.as<Button>().SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
|
||||
btnPickShortcut.as<Button>().SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
|
||||
|
||||
shortcutControlLayout.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
|
||||
|
||||
keyComboAndSelectStackPanel = StackPanel();
|
||||
keyComboAndSelectStackPanel.as<StackPanel>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
|
||||
keyComboAndSelectStackPanel.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
|
||||
keyComboStackPanel = StackPanel();
|
||||
keyComboStackPanel.as<StackPanel>().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
|
||||
keyComboStackPanel.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
|
||||
|
||||
keyComboAndSelectStackPanel.as<StackPanel>().Children().Append(typeShortcut.as<Button>());
|
||||
shortcutControlLayout.as<StackPanel>().Children().InsertAt(0, keyComboAndSelectStackPanel.as<StackPanel>());
|
||||
shortcutControlLayout.as<StackPanel>().Children().Append(keyComboStackPanel.as<StackPanel>());
|
||||
|
||||
spBtnPickShortcut = UIHelpers::GetLabelWrapped(btnPickShortcut.as<Button>(), GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_SHORTCUT), 80).as<StackPanel>();
|
||||
shortcutControlLayout.as<StackPanel>().Children().Append(spBtnPickShortcut);
|
||||
|
||||
shortcutControlLayout.as<StackPanel>().Children().Append(shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
|
||||
KeyDropDownControl::AddDropDown(table, row, shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), colIndex, shortcutRemapBuffer, keyDropDownControlObjects, targetApp, isHybridControl, false);
|
||||
|
||||
try
|
||||
{
|
||||
// If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
|
||||
@ -60,6 +79,13 @@ ShortcutControl::ShortcutControl(StackPanel table, StackPanel row, const int col
|
||||
}
|
||||
}
|
||||
|
||||
void ShortcutControl::OpenNewShortcutControlRow(StackPanel table, StackPanel row)
|
||||
{
|
||||
keyboardManagerState->SetUIState(KBMEditor::KeyboardManagerUIState::DetectShortcutWindowActivated, editShortcutsWindowHandle);
|
||||
// Using the XamlRoot of the typeShortcut to get the root of the XAML host
|
||||
CreateDetectShortcutWindow(btnPickShortcut, btnPickShortcut.XamlRoot(), *keyboardManagerState, 0, table, keyDropDownControlObjects, row, nullptr, false, false, editShortcutsWindowHandle, shortcutRemapBuffer);
|
||||
}
|
||||
|
||||
// Function to set the accessible name of the target App text box
|
||||
void ShortcutControl::SetAccessibleNameForTextBox(TextBox targetAppTextBox, int rowIndex)
|
||||
{
|
||||
@ -82,18 +108,36 @@ void ShortcutControl::UpdateAccessibleNames(StackPanel sourceColumn, StackPanel
|
||||
deleteButton.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_AUTOMATIONPROPERTIES_ROW) + std::to_wstring(rowIndex) + L", " + GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
|
||||
}
|
||||
|
||||
void ShortcutControl::DeleteShortcutControl(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, int rowIndex)
|
||||
{
|
||||
UIElementCollection children = parent.Children();
|
||||
children.RemoveAt(rowIndex);
|
||||
shortcutRemapBuffer.erase(shortcutRemapBuffer.begin() + rowIndex);
|
||||
// delete the SingleKeyRemapControl objects so that they get destructed
|
||||
keyboardRemapControlObjects.erase(keyboardRemapControlObjects.begin() + rowIndex);
|
||||
}
|
||||
|
||||
// Function to add a new row to the shortcut table. If the originalKeys and newKeys args are provided, then the displayed shortcuts are set to those values.
|
||||
void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys, const KeyShortcutTextUnion& newKeys, const std::wstring& targetAppName)
|
||||
ShortcutControl& ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys, const KeyShortcutTextUnion& newKeys, const std::wstring& targetAppName)
|
||||
{
|
||||
// Textbox for target application
|
||||
TextBox targetAppTextBox;
|
||||
int runProgramLabelWidth = 80;
|
||||
|
||||
// Create new ShortcutControl objects dynamically so that we does not get destructed
|
||||
std::vector<std::unique_ptr<ShortcutControl>> newrow;
|
||||
StackPanel row = StackPanel();
|
||||
|
||||
row.Name(L"row");
|
||||
|
||||
parent.Children().Append(row);
|
||||
|
||||
newrow.emplace_back(std::make_unique<ShortcutControl>(parent, row, 0, targetAppTextBox));
|
||||
|
||||
ShortcutControl& newShortcutToRemap = *(newrow.back());
|
||||
|
||||
newrow.emplace_back(std::make_unique<ShortcutControl>(parent, row, 1, targetAppTextBox));
|
||||
|
||||
keyboardRemapControlObjects.push_back(std::move(newrow));
|
||||
|
||||
row.Padding({ 10, 15, 10, 5 });
|
||||
@ -105,7 +149,9 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
|
||||
// ShortcutControl for the original shortcut
|
||||
auto origin = keyboardRemapControlObjects.back()[0]->GetShortcutControl();
|
||||
|
||||
origin.Width(EditorConstants::ShortcutOriginColumnWidth);
|
||||
|
||||
row.Children().Append(origin);
|
||||
|
||||
// Arrow icon
|
||||
@ -122,26 +168,46 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
auto target = keyboardRemapControlObjects.back()[1]->GetShortcutControl();
|
||||
target.Width(EditorConstants::ShortcutTargetColumnWidth);
|
||||
|
||||
auto typeCombo = ComboBox();
|
||||
typeCombo.Width(EditorConstants::RemapTableDropDownWidth);
|
||||
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeKeyShortcut()));
|
||||
typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
|
||||
uint32_t rowIndex = -1;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
{
|
||||
return newShortcutToRemap;
|
||||
}
|
||||
|
||||
// add shortcut type choice
|
||||
auto actionTypeCombo = ComboBox();
|
||||
actionTypeCombo.Name(L"actionTypeCombo_" + std::to_wstring(rowIndex));
|
||||
actionTypeCombo.Width(EditorConstants::RemapTableDropDownWidth);
|
||||
actionTypeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeKeyShortcut()));
|
||||
actionTypeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
|
||||
actionTypeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeRunProgram()));
|
||||
actionTypeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeOpenUri()));
|
||||
|
||||
auto controlStackPanel = keyboardRemapControlObjects.back()[1]->shortcutControlLayout.as<StackPanel>();
|
||||
auto firstLineStackPanel = keyboardRemapControlObjects.back()[1]->keyComboAndSelectStackPanel.as<StackPanel>();
|
||||
firstLineStackPanel.Children().InsertAt(0, typeCombo);
|
||||
auto firstLineStackPanel = keyboardRemapControlObjects.back()[1]->keyComboStackPanel.as<StackPanel>();
|
||||
|
||||
firstLineStackPanel.Children().InsertAt(0, UIHelpers::GetLabelWrapped(actionTypeCombo, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_ACTION), runProgramLabelWidth).as<StackPanel>());
|
||||
|
||||
// add textbox for when it's a text input
|
||||
|
||||
auto unicodeTextKeysInput = TextBox();
|
||||
|
||||
unicodeTextKeysInput.Name(L"unicodeTextKeysInput_" + std::to_wstring(rowIndex));
|
||||
|
||||
auto textInput = TextBox();
|
||||
auto textInputMargin = Windows::UI::Xaml::Thickness();
|
||||
textInputMargin.Top = -EditorConstants::ShortcutTableDropDownSpacing;
|
||||
textInputMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed UIElement
|
||||
textInput.Margin(textInputMargin);
|
||||
unicodeTextKeysInput.Margin(textInputMargin);
|
||||
|
||||
textInput.AcceptsReturn(false);
|
||||
textInput.Visibility(Visibility::Collapsed);
|
||||
textInput.Width(EditorConstants::TableDropDownHeight);
|
||||
controlStackPanel.Children().Append(textInput);
|
||||
textInput.HorizontalAlignment(HorizontalAlignment::Left);
|
||||
textInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
|
||||
unicodeTextKeysInput.AcceptsReturn(false);
|
||||
//unicodeTextKeysInput.Visibility(Visibility::Collapsed);
|
||||
unicodeTextKeysInput.Width(EditorConstants::TableDropDownHeight);
|
||||
|
||||
StackPanel spUnicodeTextKeysInput = UIHelpers::GetLabelWrapped(unicodeTextKeysInput, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_KEYS), runProgramLabelWidth).as<StackPanel>();
|
||||
controlStackPanel.Children().Append(spUnicodeTextKeysInput);
|
||||
|
||||
unicodeTextKeysInput.HorizontalAlignment(HorizontalAlignment::Left);
|
||||
|
||||
unicodeTextKeysInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
|
||||
auto textbox = sender.as<TextBox>();
|
||||
auto text = textbox.Text();
|
||||
uint32_t rowIndex = -1;
|
||||
@ -154,29 +220,93 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
shortcutRemapBuffer[rowIndex].first[1] = text.c_str();
|
||||
});
|
||||
|
||||
auto grid = keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>();
|
||||
const bool textSelected = newKeys.index() == 2;
|
||||
bool isRunProgram = false;
|
||||
bool isOpenUri = false;
|
||||
Shortcut shortCut;
|
||||
if (!textSelected && newKeys.index() == 1)
|
||||
{
|
||||
shortCut = std::get<Shortcut>(newKeys);
|
||||
isRunProgram = (shortCut.operationType == Shortcut::OperationType::RunProgram);
|
||||
isOpenUri = (shortCut.operationType == Shortcut::OperationType::OpenURI);
|
||||
}
|
||||
|
||||
// add TextBoxes for when it's a runProgram fields
|
||||
|
||||
auto runProgramStackPanel = SetupRunProgramControls(parent, row, shortCut, textInputMargin, controlStackPanel);
|
||||
|
||||
runProgramStackPanel.Margin({ 0, -30, 0, 0 });
|
||||
|
||||
auto openURIStackPanel = SetupOpenURIControls(parent, row, shortCut, textInputMargin, controlStackPanel);
|
||||
|
||||
// add grid for when it's a key/shortcut
|
||||
auto shortcutItemsGrid = keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>();
|
||||
auto gridMargin = Windows::UI::Xaml::Thickness();
|
||||
gridMargin.Bottom = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed textInput
|
||||
grid.Margin(gridMargin);
|
||||
auto button = keyboardRemapControlObjects.back()[1]->typeShortcut.as<Button>();
|
||||
shortcutItemsGrid.Margin(gridMargin);
|
||||
auto shortcutButton = keyboardRemapControlObjects.back()[1]->btnPickShortcut.as<Button>();
|
||||
auto spBtnPickShortcut = keyboardRemapControlObjects.back()[1]->spBtnPickShortcut.as<StackPanel>();
|
||||
|
||||
typeCombo.SelectionChanged([typeCombo, grid, button, textInput](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
|
||||
const bool textSelected = typeCombo.SelectedIndex() == 1;
|
||||
// event code for when type changes
|
||||
actionTypeCombo.SelectionChanged([parent, row, controlStackPanel, actionTypeCombo, shortcutItemsGrid, spBtnPickShortcut, spUnicodeTextKeysInput, runProgramStackPanel, openURIStackPanel](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
|
||||
const auto shortcutType = ShortcutControl::GetShortcutType(actionTypeCombo);
|
||||
|
||||
const auto shortcutInputVisibility = textSelected ? Visibility::Collapsed : Visibility::Visible;
|
||||
|
||||
grid.Visibility(shortcutInputVisibility);
|
||||
button.Visibility(shortcutInputVisibility);
|
||||
|
||||
const auto textInputVisibility = textSelected ? Visibility::Visible : Visibility::Collapsed;
|
||||
textInput.Visibility(textInputVisibility);
|
||||
if (shortcutType == ShortcutControl::ShortcutType::Shortcut)
|
||||
{
|
||||
spBtnPickShortcut.Visibility(Visibility::Visible);
|
||||
shortcutItemsGrid.Visibility(Visibility::Visible);
|
||||
spUnicodeTextKeysInput.Visibility(Visibility::Collapsed);
|
||||
runProgramStackPanel.Visibility(Visibility::Collapsed);
|
||||
openURIStackPanel.Visibility(Visibility::Collapsed);
|
||||
}
|
||||
else if (shortcutType == ShortcutControl::ShortcutType::Text)
|
||||
{
|
||||
spBtnPickShortcut.Visibility(Visibility::Collapsed);
|
||||
shortcutItemsGrid.Visibility(Visibility::Collapsed);
|
||||
spUnicodeTextKeysInput.Visibility(Visibility::Visible);
|
||||
runProgramStackPanel.Visibility(Visibility::Collapsed);
|
||||
openURIStackPanel.Visibility(Visibility::Collapsed);
|
||||
}
|
||||
else if (shortcutType == ShortcutControl::ShortcutType::RunProgram)
|
||||
{
|
||||
spBtnPickShortcut.Visibility(Visibility::Collapsed);
|
||||
shortcutItemsGrid.Visibility(Visibility::Collapsed);
|
||||
spUnicodeTextKeysInput.Visibility(Visibility::Collapsed);
|
||||
runProgramStackPanel.Visibility(Visibility::Visible);
|
||||
openURIStackPanel.Visibility(Visibility::Collapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
spBtnPickShortcut.Visibility(Visibility::Collapsed);
|
||||
shortcutItemsGrid.Visibility(Visibility::Collapsed);
|
||||
spUnicodeTextKeysInput.Visibility(Visibility::Collapsed);
|
||||
runProgramStackPanel.Visibility(Visibility::Collapsed);
|
||||
openURIStackPanel.Visibility(Visibility::Visible);
|
||||
}
|
||||
});
|
||||
|
||||
const bool textSelected = newKeys.index() == 2;
|
||||
typeCombo.SelectedIndex(textSelected);
|
||||
|
||||
row.Children().Append(target);
|
||||
|
||||
if (textSelected)
|
||||
{
|
||||
actionTypeCombo.SelectedIndex(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shortCut.operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
actionTypeCombo.SelectedIndex(2);
|
||||
}
|
||||
else if (shortCut.operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
actionTypeCombo.SelectedIndex(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
actionTypeCombo.SelectedIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
targetAppTextBox.Width(EditorConstants::ShortcutTableDropDownWidth);
|
||||
targetAppTextBox.PlaceholderText(KeyboardManagerEditorStrings::DefaultAppName());
|
||||
targetAppTextBox.Text(targetAppName);
|
||||
@ -189,7 +319,7 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
});
|
||||
|
||||
// LostFocus handler will be called whenever text is updated by a user and then they click something else or tab to another control. Does not get called if Text is updated while the TextBox isn't in focus (i.e. from code)
|
||||
targetAppTextBox.LostFocus([&keyboardRemapControlObjects, parent, row, targetAppTextBox, typeCombo, textInput](auto const& sender, auto const& e) {
|
||||
targetAppTextBox.LostFocus([&keyboardRemapControlObjects, parent, row, targetAppTextBox, actionTypeCombo, unicodeTextKeysInput](auto const& sender, auto const& e) {
|
||||
// Get index of targetAppTextBox button
|
||||
uint32_t rowIndex;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
@ -211,28 +341,60 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[0]).SetKeyCodes(KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][0]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>()));
|
||||
// second column is a hybrid column
|
||||
|
||||
const bool textSelected = typeCombo.SelectedIndex() == 1;
|
||||
const bool regularShortcut = actionTypeCombo.SelectedIndex() == 0;
|
||||
const bool textSelected = actionTypeCombo.SelectedIndex() == 1;
|
||||
const bool runProgram = actionTypeCombo.SelectedIndex() == 2;
|
||||
const bool openUri = actionTypeCombo.SelectedIndex() == 3;
|
||||
|
||||
if (textSelected)
|
||||
{
|
||||
shortcutRemapBuffer[rowIndex].first[1] = textInput.Text().c_str();
|
||||
shortcutRemapBuffer[rowIndex].first[1] = unicodeTextKeysInput.Text().c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<int32_t> selectedKeyCodes = KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
|
||||
if (regularShortcut)
|
||||
{
|
||||
std::vector<int32_t> selectedKeyCodes = KeyDropDownControl::GetSelectedCodesFromStackPanel(keyboardRemapControlObjects[rowIndex][1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>());
|
||||
|
||||
// If exactly one key is selected consider it to be a key remap
|
||||
if (selectedKeyCodes.size() == 1)
|
||||
{
|
||||
shortcutRemapBuffer[rowIndex].first[1] = (DWORD)selectedKeyCodes[0];
|
||||
// If exactly one key is selected consider it to be a key remap
|
||||
if (selectedKeyCodes.size() == 1)
|
||||
{
|
||||
shortcutRemapBuffer[rowIndex].first[1] = (DWORD)selectedKeyCodes[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
Shortcut tempShortcut;
|
||||
tempShortcut.SetKeyCodes(selectedKeyCodes);
|
||||
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
|
||||
shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (runProgram)
|
||||
{
|
||||
auto runProgramPathInput = row.FindName(L"runProgramPathInput_" + std::to_wstring(rowIndex)).as<TextBox>();
|
||||
auto runProgramArgsForProgramInput = row.FindName(L"runProgramArgsForProgramInput_" + std::to_wstring(rowIndex)).as<TextBox>();
|
||||
auto runProgramStartInDirInput = row.FindName(L"runProgramStartInDirInput_" + std::to_wstring(rowIndex)).as<TextBox>();
|
||||
auto runProgramElevationTypeCombo = row.FindName(L"runProgramElevationTypeCombo_" + std::to_wstring(rowIndex)).as<ComboBox>();
|
||||
auto runProgramAlreadyRunningAction = row.FindName(L"runProgramAlreadyRunningAction_" + std::to_wstring(rowIndex)).as<ComboBox>();
|
||||
|
||||
Shortcut tempShortcut;
|
||||
tempShortcut.SetKeyCodes(selectedKeyCodes);
|
||||
tempShortcut.operationType = Shortcut::OperationType::RunProgram;
|
||||
|
||||
tempShortcut.runProgramFilePath = ShortcutControl::RemoveExtraQuotes(runProgramPathInput.Text().c_str());
|
||||
tempShortcut.runProgramArgs = (runProgramArgsForProgramInput.Text().c_str());
|
||||
tempShortcut.runProgramStartInDir = (runProgramStartInDirInput.Text().c_str());
|
||||
|
||||
tempShortcut.elevationLevel = static_cast<Shortcut::ElevationLevel>(runProgramElevationTypeCombo.SelectedIndex());
|
||||
tempShortcut.alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(runProgramAlreadyRunningAction.SelectedIndex());
|
||||
|
||||
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
|
||||
shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
}
|
||||
else if (openUri)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring newText = targetAppTextBox.Text().c_str();
|
||||
std::wstring lowercaseDefAppName = KeyboardManagerEditorStrings::DefaultAppName();
|
||||
std::transform(newText.begin(), newText.end(), newText.begin(), towlower);
|
||||
@ -251,16 +413,19 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
});
|
||||
|
||||
// We need two containers in order to align it horizontally and vertically
|
||||
|
||||
StackPanel targetAppHorizontal = UIHelpers::GetWrapped(targetAppTextBox, EditorConstants::TableTargetAppColWidth).as<StackPanel>();
|
||||
targetAppHorizontal.Orientation(Orientation::Horizontal);
|
||||
targetAppHorizontal.HorizontalAlignment(HorizontalAlignment::Left);
|
||||
StackPanel targetAppContainer = UIHelpers::GetWrapped(targetAppHorizontal, EditorConstants::TableTargetAppColWidth).as<StackPanel>();
|
||||
targetAppContainer.Orientation(Orientation::Vertical);
|
||||
targetAppContainer.VerticalAlignment(VerticalAlignment::Center);
|
||||
|
||||
row.Children().Append(targetAppContainer);
|
||||
|
||||
// Delete row button
|
||||
Windows::UI::Xaml::Controls::Button deleteShortcut;
|
||||
|
||||
deleteShortcut.Content(SymbolIcon(Symbol::Delete));
|
||||
deleteShortcut.Background(Media::SolidColorBrush(Colors::Transparent()));
|
||||
deleteShortcut.HorizontalAlignment(HorizontalAlignment::Center);
|
||||
@ -312,9 +477,11 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
ToolTipService::SetToolTip(deleteShortcut, deleteShortcuttoolTip);
|
||||
|
||||
StackPanel deleteShortcutContainer = StackPanel();
|
||||
deleteShortcutContainer.Name(L"deleteShortcutContainer");
|
||||
deleteShortcutContainer.Children().Append(deleteShortcut);
|
||||
deleteShortcutContainer.Orientation(Orientation::Vertical);
|
||||
deleteShortcutContainer.VerticalAlignment(VerticalAlignment::Center);
|
||||
|
||||
row.Children().Append(deleteShortcutContainer);
|
||||
|
||||
// Set accessible names
|
||||
@ -324,12 +491,25 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
if (EditorHelpers::IsValidShortcut(originalKeys) && !(newKeys.index() == 0 && std::get<DWORD>(newKeys) == NULL) && !(newKeys.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKeys))))
|
||||
{
|
||||
// change to load app name
|
||||
shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
|
||||
|
||||
if (isRunProgram || isOpenUri)
|
||||
{
|
||||
// not sure why by we need to add the current item in here, so we have it even if does not change.
|
||||
auto newShortcut = std::get<Shortcut>(newKeys);
|
||||
shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), newShortcut }, std::wstring(targetAppName)));
|
||||
}
|
||||
else
|
||||
{
|
||||
shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
|
||||
}
|
||||
|
||||
KeyDropDownControl::AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 0, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, false, false);
|
||||
|
||||
if (newKeys.index() == 0)
|
||||
{
|
||||
keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(std::get<DWORD>(newKeys)));
|
||||
auto shortcut = new Shortcut;
|
||||
shortcut->SetKey(std::get<DWORD>(newKeys));
|
||||
KeyDropDownControl::AddShortcutToControl(*shortcut, parent, keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 1, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, true, false);
|
||||
}
|
||||
else if (newKeys.index() == 1)
|
||||
{
|
||||
@ -339,16 +519,404 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector<s
|
||||
{
|
||||
shortcutRemapBuffer.back().first[1] = std::get<std::wstring>(newKeys);
|
||||
const auto& remapControl = keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1];
|
||||
const auto& controlChildren = remapControl->GetShortcutControl().Children();
|
||||
const auto& topLineChildren = controlChildren.GetAt(0).as<StackPanel>();
|
||||
topLineChildren.Children().GetAt(0).as<ComboBox>().SelectedIndex(1);
|
||||
controlChildren.GetAt(2).as<TextBox>().Text(std::get<std::wstring>(newKeys));
|
||||
actionTypeCombo.SelectedIndex(1);
|
||||
unicodeTextKeysInput.Text(std::get<std::wstring>(newKeys));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Initialize both shortcuts as empty shortcuts
|
||||
shortcutRemapBuffer.push_back(std::make_pair<RemapBufferItem, std::wstring>(RemapBufferItem{ Shortcut(), Shortcut() }, std::wstring(targetAppName)));
|
||||
|
||||
KeyDropDownControl::AddShortcutToControl(originalKeys, parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 0, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, false, false);
|
||||
|
||||
KeyDropDownControl::AddShortcutToControl(std::get<Shortcut>(newKeys), parent, keyboardRemapControlObjects.back()[1]->shortcutDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 1, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects, shortcutRemapBuffer, row, targetAppTextBox, true, false);
|
||||
|
||||
}
|
||||
|
||||
return newShortcutToRemap;
|
||||
}
|
||||
|
||||
StackPanel SetupOpenURIControls(StackPanel& parent, StackPanel& row, Shortcut& shortCut, winrt::Windows::UI::Xaml::Thickness& textInputMargin, ::StackPanel& _controlStackPanel)
|
||||
{
|
||||
StackPanel openUriStackPanel;
|
||||
auto uriTextBox = TextBox();
|
||||
|
||||
int runProgramLabelWidth = 80;
|
||||
|
||||
uriTextBox.Text(shortCut.uriToOpen);
|
||||
uriTextBox.PlaceholderText(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_URI_EXAMPLE));
|
||||
uriTextBox.Margin(textInputMargin);
|
||||
uriTextBox.Width(EditorConstants::TableDropDownHeight);
|
||||
uriTextBox.HorizontalAlignment(HorizontalAlignment::Left);
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::HyperlinkButton hyperlinkButton;
|
||||
hyperlinkButton.NavigateUri(Windows::Foundation::Uri(L"https://learn.microsoft.com/windows/uwp/launch-resume/launch-app-with-uri"));
|
||||
hyperlinkButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_WHAT_CAN_I_USE_LINK)));
|
||||
hyperlinkButton.Margin(textInputMargin);
|
||||
|
||||
StackPanel boxAndLink;
|
||||
boxAndLink.Orientation(Orientation::Horizontal);
|
||||
boxAndLink.Children().Append(uriTextBox);
|
||||
boxAndLink.Children().Append(hyperlinkButton);
|
||||
|
||||
openUriStackPanel.Children().Append(UIHelpers::GetLabelWrapped(boxAndLink, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_PATH_URI), runProgramLabelWidth).as<StackPanel>());
|
||||
|
||||
uriTextBox.TextChanged([parent, row, uriTextBox](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
|
||||
uint32_t rowIndex = -1;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Shortcut tempShortcut;
|
||||
tempShortcut.operationType = Shortcut::OperationType::OpenURI;
|
||||
tempShortcut.uriToOpen = ShortcutControl::RemoveExtraQuotes(uriTextBox.Text().c_str());
|
||||
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
});
|
||||
|
||||
_controlStackPanel.Children().Append(openUriStackPanel);
|
||||
return openUriStackPanel;
|
||||
}
|
||||
|
||||
StackPanel SetupRunProgramControls(StackPanel& parent, StackPanel& row, Shortcut& shortCut, winrt::Windows::UI::Xaml::Thickness& textInputMargin, ::StackPanel& _controlStackPanel)
|
||||
{
|
||||
uint32_t rowIndex;
|
||||
// Get index of delete button
|
||||
UIElementCollection children = parent.Children();
|
||||
children.IndexOf(row, rowIndex);
|
||||
|
||||
StackPanel controlStackPanel;
|
||||
controlStackPanel.Name(L"RunProgramControls_" + std::to_wstring(rowIndex));
|
||||
|
||||
auto runProgramPathInput = TextBox();
|
||||
runProgramPathInput.Name(L"runProgramPathInput_" + std::to_wstring(rowIndex));
|
||||
auto runProgramArgsForProgramInput = TextBox();
|
||||
runProgramArgsForProgramInput.Name(L"runProgramArgsForProgramInput_" + std::to_wstring(rowIndex));
|
||||
auto runProgramStartInDirInput = TextBox();
|
||||
runProgramStartInDirInput.Name(L"runProgramStartInDirInput_" + std::to_wstring(rowIndex));
|
||||
|
||||
Button pickFileBtn;
|
||||
Button pickPathBtn;
|
||||
auto runProgramElevationTypeCombo = ComboBox();
|
||||
runProgramElevationTypeCombo.Name(L"runProgramElevationTypeCombo_" + std::to_wstring(rowIndex));
|
||||
|
||||
auto runProgramAlreadyRunningAction = ComboBox();
|
||||
runProgramAlreadyRunningAction.Name(L"runProgramAlreadyRunningAction_" + std::to_wstring(rowIndex));
|
||||
|
||||
_controlStackPanel.Children().Append(controlStackPanel);
|
||||
|
||||
StackPanel stackPanelForRunProgramPath;
|
||||
StackPanel stackPanelRunProgramStartInDir;
|
||||
|
||||
runProgramPathInput.PlaceholderText(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_PATH_TO_PROGRAM));
|
||||
|
||||
runProgramPathInput.Margin(textInputMargin);
|
||||
|
||||
runProgramPathInput.AcceptsReturn(false);
|
||||
runProgramPathInput.IsSpellCheckEnabled(false);
|
||||
runProgramPathInput.Width(EditorConstants::TableDropDownHeight);
|
||||
runProgramPathInput.HorizontalAlignment(HorizontalAlignment::Left);
|
||||
|
||||
runProgramArgsForProgramInput.PlaceholderText(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_ARGS_FOR_PROGRAM));
|
||||
runProgramArgsForProgramInput.Margin(textInputMargin);
|
||||
runProgramArgsForProgramInput.AcceptsReturn(false);
|
||||
runProgramArgsForProgramInput.IsSpellCheckEnabled(false);
|
||||
runProgramArgsForProgramInput.Width(EditorConstants::TableDropDownHeight);
|
||||
runProgramArgsForProgramInput.HorizontalAlignment(HorizontalAlignment::Left);
|
||||
|
||||
runProgramStartInDirInput.IsSpellCheckEnabled(false);
|
||||
runProgramStartInDirInput.PlaceholderText(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_START_IN_DIR_FOR_PROGRAM));
|
||||
runProgramStartInDirInput.Margin(textInputMargin);
|
||||
runProgramStartInDirInput.AcceptsReturn(false);
|
||||
runProgramStartInDirInput.Width(EditorConstants::TableDropDownHeight);
|
||||
runProgramStartInDirInput.HorizontalAlignment(HorizontalAlignment::Left);
|
||||
|
||||
stackPanelForRunProgramPath.Orientation(Orientation::Horizontal);
|
||||
stackPanelRunProgramStartInDir.Orientation(Orientation::Horizontal);
|
||||
|
||||
pickFileBtn.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_BROWSE_FOR_PROGRAM_BUTTON)));
|
||||
pickPathBtn.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_BROWSE_FOR_PATH_BUTTON)));
|
||||
pickFileBtn.Margin(textInputMargin);
|
||||
pickPathBtn.Margin(textInputMargin);
|
||||
|
||||
stackPanelForRunProgramPath.Children().Append(runProgramPathInput);
|
||||
stackPanelForRunProgramPath.Children().Append(pickFileBtn);
|
||||
|
||||
stackPanelRunProgramStartInDir.Children().Append(runProgramStartInDirInput);
|
||||
stackPanelRunProgramStartInDir.Children().Append(pickPathBtn);
|
||||
|
||||
int runProgramLabelWidth = 90;
|
||||
|
||||
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(stackPanelForRunProgramPath, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_PROGRAM), runProgramLabelWidth).as<StackPanel>());
|
||||
|
||||
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(runProgramArgsForProgramInput, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_ARGS), runProgramLabelWidth).as<StackPanel>());
|
||||
|
||||
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(stackPanelRunProgramStartInDir, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_START_IN), runProgramLabelWidth).as<StackPanel>());
|
||||
|
||||
// add shortcut type choice
|
||||
runProgramElevationTypeCombo.Width(EditorConstants::TableDropDownHeight);
|
||||
runProgramElevationTypeCombo.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ELEVATION_TYPE_NORMAL)));
|
||||
runProgramElevationTypeCombo.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ELEVATION_TYPE_ELEVATED)));
|
||||
runProgramElevationTypeCombo.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ELEVATION_TYPE_DIFFERENT_USER)));
|
||||
runProgramElevationTypeCombo.SelectedIndex(0);
|
||||
// runProgramAlreadyRunningAction
|
||||
runProgramAlreadyRunningAction.Width(EditorConstants::TableDropDownHeight);
|
||||
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_SHOW_WINDOW)));
|
||||
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_START_ANOTHER)));
|
||||
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_DO_NOTHING)));
|
||||
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_CLOSE)));
|
||||
runProgramAlreadyRunningAction.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_ALREADY_RUNNING_TERMINATE)));
|
||||
|
||||
runProgramAlreadyRunningAction.SelectedIndex(0);
|
||||
|
||||
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(runProgramElevationTypeCombo, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_ELEVATION), runProgramLabelWidth).as<StackPanel>());
|
||||
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(runProgramAlreadyRunningAction, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_IF_RUNNING), runProgramLabelWidth).as<StackPanel>());
|
||||
|
||||
auto runProgramStartWindow = ComboBox();
|
||||
runProgramStartWindow.Name(L"runProgramStartWindow_" + std::to_wstring(rowIndex));
|
||||
runProgramStartWindow.Width(EditorConstants::TableDropDownHeight);
|
||||
runProgramStartWindow.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_VISIBILITY_NORMAL)));
|
||||
runProgramStartWindow.Items().Append(winrt::box_value(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_VISIBILITY_HIDDEN)));
|
||||
runProgramStartWindow.SelectedIndex(0);
|
||||
controlStackPanel.Children().Append(UIHelpers::GetLabelWrapped(runProgramStartWindow, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_LABEL_START_AS), runProgramLabelWidth).as<StackPanel>());
|
||||
|
||||
// add events to TextBoxes for runProgram fields.
|
||||
runProgramPathInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
|
||||
uint32_t rowIndex = -1;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
Shortcut tempShortcut;
|
||||
CreateNewTempShortcut(row, tempShortcut, rowIndex);
|
||||
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
});
|
||||
|
||||
runProgramArgsForProgramInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
|
||||
uint32_t rowIndex = -1;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Shortcut tempShortcut;
|
||||
CreateNewTempShortcut(row, tempShortcut, rowIndex);
|
||||
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
});
|
||||
|
||||
runProgramStartInDirInput.TextChanged([parent, row](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
|
||||
uint32_t rowIndex = -1;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShortcutControl::shortcutRemapBuffer.size() <= rowIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Shortcut tempShortcut;
|
||||
CreateNewTempShortcut(row, tempShortcut, rowIndex);
|
||||
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
});
|
||||
|
||||
runProgramAlreadyRunningAction.SelectionChanged([parent, row](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
|
||||
uint32_t rowIndex;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShortcutControl::shortcutRemapBuffer.size() <= rowIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Shortcut tempShortcut;
|
||||
CreateNewTempShortcut(static_cast<StackPanel>(row), tempShortcut, rowIndex);
|
||||
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
});
|
||||
|
||||
runProgramElevationTypeCombo.SelectionChanged([parent, row](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
|
||||
uint32_t rowIndex;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShortcutControl::shortcutRemapBuffer.size() <= rowIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Shortcut tempShortcut;
|
||||
CreateNewTempShortcut(static_cast<StackPanel>(row), tempShortcut, rowIndex);
|
||||
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
});
|
||||
|
||||
runProgramStartWindow.SelectionChanged([parent, row](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
|
||||
uint32_t rowIndex;
|
||||
if (!parent.Children().IndexOf(row, rowIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ShortcutControl::shortcutRemapBuffer.size() <= rowIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Shortcut tempShortcut;
|
||||
CreateNewTempShortcut(static_cast<StackPanel>(row), tempShortcut, rowIndex);
|
||||
|
||||
ShortcutControl::shortcutRemapBuffer[rowIndex].first[1] = tempShortcut;
|
||||
});
|
||||
|
||||
pickFileBtn.Click([&, parent, row](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
Button currentButton = sender.as<Button>();
|
||||
uint32_t rowIndex;
|
||||
UIElementCollection children = parent.Children();
|
||||
bool indexFound = children.IndexOf(row, rowIndex);
|
||||
|
||||
if (!indexFound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
OPENFILENAME openFileName;
|
||||
TCHAR szFile[260] = { 0 };
|
||||
|
||||
ZeroMemory(&openFileName, sizeof(openFileName));
|
||||
openFileName.lStructSize = sizeof(openFileName);
|
||||
openFileName.hwndOwner = NULL;
|
||||
openFileName.lpstrFile = szFile;
|
||||
openFileName.nMaxFile = sizeof(szFile);
|
||||
openFileName.lpstrFilter = TEXT("All Files (*.*)\0*.*\0");
|
||||
openFileName.nFilterIndex = 1;
|
||||
openFileName.lpstrFileTitle = NULL;
|
||||
openFileName.nMaxFileTitle = 0;
|
||||
openFileName.lpstrInitialDir = NULL;
|
||||
openFileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
|
||||
|
||||
auto runProgramPathInput = row.FindName(L"runProgramPathInput_" + std::to_wstring(rowIndex)).as<TextBox>();
|
||||
|
||||
if (GetOpenFileName(&openFileName) == TRUE)
|
||||
{
|
||||
runProgramPathInput.Text(szFile);
|
||||
}
|
||||
});
|
||||
|
||||
pickPathBtn.Click([&, parent, row](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
|
||||
Button currentButton = sender.as<Button>();
|
||||
uint32_t rowIndex;
|
||||
UIElementCollection children = parent.Children();
|
||||
bool indexFound = children.IndexOf(row, rowIndex);
|
||||
if (!indexFound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
|
||||
if (!FAILED(hr))
|
||||
{
|
||||
// Create a buffer to store the selected folder path
|
||||
wchar_t path[MAX_PATH];
|
||||
ZeroMemory(path, sizeof(path));
|
||||
|
||||
// Initialize the BROWSEINFO structure
|
||||
BROWSEINFO browseInfo = { 0 };
|
||||
browseInfo.hwndOwner = NULL; // Use NULL if there's no owner window
|
||||
browseInfo.pidlRoot = NULL; // Use NULL to start from the desktop
|
||||
browseInfo.pszDisplayName = path; // Buffer to store the display name
|
||||
browseInfo.lpszTitle = L"Select a folder"; // Title of the dialog
|
||||
browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE; // Show only file system directories
|
||||
|
||||
// Show the dialog
|
||||
|
||||
LPITEMIDLIST pidl = SHBrowseForFolder(&browseInfo);
|
||||
if (pidl != NULL)
|
||||
{
|
||||
// Get the selected folder's path
|
||||
if (SHGetPathFromIDList(pidl, path))
|
||||
{
|
||||
auto runProgramStartInDirInput = row.FindName(L"runProgramStartInDirInput_" + std::to_wstring(rowIndex)).as<TextBox>();
|
||||
runProgramStartInDirInput.Text(path);
|
||||
}
|
||||
|
||||
// Free the PIDL
|
||||
CoTaskMemFree(pidl);
|
||||
}
|
||||
|
||||
// Release COM
|
||||
CoUninitialize();
|
||||
|
||||
// Uninitialize COM
|
||||
CoUninitialize();
|
||||
}
|
||||
});
|
||||
|
||||
// this really should not be here, it just works because SelectionChanged is always changed?
|
||||
runProgramPathInput.Text(shortCut.runProgramFilePath);
|
||||
runProgramArgsForProgramInput.Text(shortCut.runProgramArgs);
|
||||
runProgramStartInDirInput.Text(shortCut.runProgramStartInDir);
|
||||
|
||||
runProgramElevationTypeCombo.SelectedIndex(shortCut.elevationLevel);
|
||||
runProgramAlreadyRunningAction.SelectedIndex(shortCut.alreadyRunningAction);
|
||||
runProgramStartWindow.SelectedIndex(shortCut.startWindowType);
|
||||
|
||||
return controlStackPanel;
|
||||
}
|
||||
|
||||
void CreateNewTempShortcut(StackPanel& row, Shortcut& tempShortcut, const uint32_t& rowIndex)
|
||||
{
|
||||
tempShortcut.operationType = Shortcut::OperationType::RunProgram;
|
||||
//tempShortcut.isRunProgram = true;
|
||||
|
||||
auto runProgramPathInput = row.FindName(L"runProgramPathInput_" + std::to_wstring(rowIndex)).as<TextBox>();
|
||||
auto runProgramArgsForProgramInput = row.FindName(L"runProgramArgsForProgramInput_" + std::to_wstring(rowIndex)).as<TextBox>();
|
||||
auto runProgramStartInDirInput = row.FindName(L"runProgramStartInDirInput_" + std::to_wstring(rowIndex)).as<TextBox>();
|
||||
auto runProgramElevationTypeCombo = row.FindName(L"runProgramElevationTypeCombo_" + std::to_wstring(rowIndex)).as<ComboBox>();
|
||||
auto runProgramAlreadyRunningAction = row.FindName(L"runProgramAlreadyRunningAction_" + std::to_wstring(rowIndex)).as<ComboBox>();
|
||||
auto runProgramStartWindow = row.FindName(L"runProgramStartWindow_" + std::to_wstring(rowIndex)).as<ComboBox>();
|
||||
|
||||
tempShortcut.runProgramFilePath = ShortcutControl::RemoveExtraQuotes(runProgramPathInput.Text().c_str());
|
||||
tempShortcut.runProgramArgs = (runProgramArgsForProgramInput.Text().c_str());
|
||||
tempShortcut.runProgramStartInDir = (runProgramStartInDirInput.Text().c_str());
|
||||
|
||||
// Assign instead of setting the value in the buffer since the previous value may not be a Shortcut
|
||||
|
||||
tempShortcut.elevationLevel = static_cast<Shortcut::ElevationLevel>(runProgramElevationTypeCombo.SelectedIndex());
|
||||
tempShortcut.alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(runProgramAlreadyRunningAction.SelectedIndex());
|
||||
tempShortcut.startWindowType = static_cast<Shortcut::StartWindowType>(runProgramStartWindow.SelectedIndex());
|
||||
}
|
||||
|
||||
std::wstring ShortcutControl::RemoveExtraQuotes(const std::wstring& str)
|
||||
{
|
||||
if (!str.empty() && str.front() == L'"' && str.back() == L'"')
|
||||
{
|
||||
return str.substr(1, str.size() - 2);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
ShortcutControl::ShortcutType ShortcutControl::GetShortcutType(const winrt::Windows::UI::Xaml::Controls::ComboBox& typeCombo)
|
||||
{
|
||||
if (typeCombo.SelectedIndex() == 0)
|
||||
{
|
||||
return ShortcutControl::ShortcutType::Shortcut;
|
||||
}
|
||||
else if (typeCombo.SelectedIndex() == 1)
|
||||
{
|
||||
return ShortcutControl::ShortcutType::Text;
|
||||
}
|
||||
else if (typeCombo.SelectedIndex() == 2)
|
||||
{
|
||||
return ShortcutControl::ShortcutType::RunProgram;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ShortcutControl::ShortcutType::OpenURI;
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,14 +929,53 @@ StackPanel ShortcutControl::GetShortcutControl()
|
||||
// Function to create the detect shortcut UI window
|
||||
void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KBMEditor::KeyboardManagerState& keyboardManagerState, const int colIndex, StackPanel table, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, StackPanel row, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, HWND parentWindow, RemapBuffer& remapBuffer)
|
||||
{
|
||||
// check to see if this orig or map-to shortcut;
|
||||
bool isOrigShortcut = (colIndex == 0);
|
||||
|
||||
uint32_t rowIndex;
|
||||
|
||||
UIElementCollection children = table.Children();
|
||||
bool indexFound = children.IndexOf(row, rowIndex);
|
||||
|
||||
Shortcut shortcut;
|
||||
|
||||
if (shortcutRemapBuffer.size() > 0)
|
||||
{
|
||||
if (colIndex == 0)
|
||||
{
|
||||
shortcut = std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (shortcutRemapBuffer[rowIndex].first[1].index() != 1)
|
||||
{
|
||||
// not a shortcut, let's fix that.
|
||||
Shortcut newShortcut;
|
||||
shortcutRemapBuffer[rowIndex].first[1] = newShortcut;
|
||||
}
|
||||
shortcut = std::get<Shortcut>(shortcutRemapBuffer[rowIndex].first[1]);
|
||||
}
|
||||
|
||||
if (!shortcut.IsEmpty() && shortcut.HasChord())
|
||||
{
|
||||
keyboardManagerState.AllowChord = true;
|
||||
} else {
|
||||
keyboardManagerState.AllowChord = false;
|
||||
}
|
||||
}
|
||||
|
||||
//remapBuffer[rowIndex].first.
|
||||
|
||||
// ContentDialog for detecting shortcuts. This is the parent UI element.
|
||||
ContentDialog detectShortcutBox;
|
||||
ToggleSwitch allowChordSwitch;
|
||||
|
||||
// ContentDialog requires manually setting the XamlRoot (https://learn.microsoft.com/uwp/api/windows.ui.xaml.controls.contentdialog#contentdialog-in-appwindow-or-xaml-islands)
|
||||
detectShortcutBox.XamlRoot(xamlRoot);
|
||||
detectShortcutBox.Title(box_value(GET_RESOURCE_STRING(IDS_TYPESHORTCUT_TITLE)));
|
||||
|
||||
// Get the parent linked stack panel for the "Type shortcut" button that was clicked
|
||||
|
||||
VariableSizedWrapGrid linkedShortcutVariableSizedWrapGrid = UIHelpers::GetSiblingElement(sender.as<FrameworkElement>().Parent()).as<VariableSizedWrapGrid>();
|
||||
|
||||
auto unregisterKeys = [&keyboardManagerState]() {
|
||||
@ -424,6 +1031,13 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
|
||||
unregisterKeys();
|
||||
};
|
||||
|
||||
auto onReleaseSpace = [&keyboardManagerState,
|
||||
allowChordSwitch] {
|
||||
|
||||
keyboardManagerState.AllowChord = !keyboardManagerState.AllowChord;
|
||||
allowChordSwitch.IsOn(keyboardManagerState.AllowChord);
|
||||
};
|
||||
|
||||
auto onAccept = [onPressEnter,
|
||||
onReleaseEnter] {
|
||||
onPressEnter();
|
||||
@ -481,6 +1095,22 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
|
||||
unregisterKeys();
|
||||
};
|
||||
|
||||
if (isOrigShortcut)
|
||||
{
|
||||
// Hold space to allow chords. Chords are only available for origin shortcuts.
|
||||
keyboardManagerState.RegisterKeyDelay(
|
||||
VK_SPACE,
|
||||
selectDetectedShortcutAndResetKeys,
|
||||
[onReleaseSpace, detectShortcutBox](DWORD) {
|
||||
detectShortcutBox.Dispatcher().RunAsync(
|
||||
Windows::UI::Core::CoreDispatcherPriority::Normal,
|
||||
[onReleaseSpace] {
|
||||
onReleaseSpace();
|
||||
});
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
|
||||
// Cancel button
|
||||
detectShortcutBox.CloseButtonText(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON));
|
||||
detectShortcutBox.CloseButtonClick([onCancel](winrt::Windows::Foundation::IInspectable const& sender, ContentDialogButtonClickEventArgs const& args) {
|
||||
@ -525,6 +1155,42 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
|
||||
keyStackPanel2.Visibility(Visibility::Collapsed);
|
||||
stackPanel.Children().Append(keyStackPanel2);
|
||||
|
||||
// Detect Chord
|
||||
Windows::UI::Xaml::Controls::StackPanel chordStackPanel;
|
||||
|
||||
if (isOrigShortcut)
|
||||
{
|
||||
constexpr double verticalMargin = 20.f;
|
||||
TextBlock allowChordText;
|
||||
allowChordText.Text(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_ALLOW_CHORDS));
|
||||
allowChordText.FontSize(12);
|
||||
allowChordText.Margin({ 0, 12 + verticalMargin, 0, 0 });
|
||||
chordStackPanel.VerticalAlignment(VerticalAlignment::Center);
|
||||
allowChordText.TextAlignment(TextAlignment::Center);
|
||||
chordStackPanel.Orientation(Orientation::Horizontal);
|
||||
|
||||
allowChordSwitch.OnContent(nullptr);
|
||||
allowChordSwitch.OffContent(nullptr);
|
||||
allowChordSwitch.Margin({ 12, verticalMargin, 0, 0 });
|
||||
|
||||
chordStackPanel.Children().Append(allowChordText);
|
||||
chordStackPanel.Children().Append(allowChordSwitch);
|
||||
|
||||
stackPanel.Children().Append(chordStackPanel);
|
||||
allowChordSwitch.IsOn(keyboardManagerState.AllowChord);
|
||||
|
||||
auto toggleHandler = [allowChordSwitch, &keyboardManagerState](auto const& sender, auto const& e) {
|
||||
keyboardManagerState.AllowChord = allowChordSwitch.IsOn();
|
||||
|
||||
if (!allowChordSwitch.IsOn())
|
||||
{
|
||||
keyboardManagerState.ClearStoredShortcut();
|
||||
}
|
||||
};
|
||||
|
||||
allowChordSwitch.Toggled(toggleHandler);
|
||||
}
|
||||
|
||||
TextBlock holdEscInfo;
|
||||
holdEscInfo.Text(GET_RESOURCE_STRING(IDS_TYPE_HOLDESC));
|
||||
holdEscInfo.FontSize(12);
|
||||
@ -537,6 +1203,16 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
|
||||
holdEnterInfo.Margin({ 0, 0, 0, 0 });
|
||||
stackPanel.Children().Append(holdEnterInfo);
|
||||
|
||||
if (isOrigShortcut)
|
||||
{
|
||||
// Hold space to allow chords. Chords are only available for origin shortcuts.
|
||||
TextBlock holdSpaceInfo;
|
||||
holdSpaceInfo.Text(GET_RESOURCE_STRING(IDS_TYPE_HOLDSPACE));
|
||||
holdSpaceInfo.FontSize(12);
|
||||
holdSpaceInfo.Margin({ 0, 0, 0, 0 });
|
||||
stackPanel.Children().Append(holdSpaceInfo);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
|
||||
@ -551,4 +1227,9 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn
|
||||
|
||||
// Show the dialog
|
||||
detectShortcutBox.ShowAsync();
|
||||
|
||||
if (!shortcut.IsEmpty() && keyboardManagerState.AllowChord)
|
||||
{
|
||||
keyboardManagerState.SetDetectedShortcut(shortcut);
|
||||
}
|
||||
}
|
||||
|
@ -26,13 +26,16 @@ private:
|
||||
winrt::Windows::Foundation::IInspectable shortcutDropDownVariableSizedWrapGrid;
|
||||
|
||||
// Button to type the shortcut
|
||||
winrt::Windows::Foundation::IInspectable typeShortcut;
|
||||
Button btnPickShortcut;
|
||||
|
||||
// StackPanel to hold the shortcut
|
||||
StackPanel spBtnPickShortcut;
|
||||
|
||||
// StackPanel to parent the above controls
|
||||
winrt::Windows::Foundation::IInspectable shortcutControlLayout;
|
||||
|
||||
// StackPanel to parent the first line of "To" Column
|
||||
winrt::Windows::Foundation::IInspectable keyComboAndSelectStackPanel;
|
||||
winrt::Windows::Foundation::IInspectable keyComboStackPanel;
|
||||
|
||||
// Function to set the accessible name of the target app text box
|
||||
static void SetAccessibleNameForTextBox(TextBox targetAppTextBox, int rowIndex);
|
||||
@ -40,6 +43,15 @@ private:
|
||||
// Function to set the accessible names for all the controls in a row
|
||||
static void UpdateAccessibleNames(StackPanel sourceColumn, StackPanel mappedToColumn, TextBox targetAppTextBox, Button deleteButton, int rowIndex);
|
||||
|
||||
// enum for the type of shortcut, to make it easier to switch on and read
|
||||
enum class ShortcutType
|
||||
{
|
||||
Shortcut,
|
||||
Text,
|
||||
RunProgram,
|
||||
OpenURI
|
||||
};
|
||||
|
||||
public:
|
||||
// Handle to the current Edit Shortcuts Window
|
||||
static HWND editShortcutsWindowHandle;
|
||||
@ -56,8 +68,20 @@ public:
|
||||
// constructor
|
||||
ShortcutControl(StackPanel table, StackPanel row, const int colIndex, TextBox targetApp);
|
||||
|
||||
// Function to that will CreateDetectShortcutWindow, created here to it can be done automatically when "new shortcut" is clicked.
|
||||
void OpenNewShortcutControlRow(StackPanel table, StackPanel row);
|
||||
|
||||
// Function to add a new row to the shortcut table. If the originalKeys and newKeys args are provided, then the displayed shortcuts are set to those values.
|
||||
static void AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys = Shortcut(), const KeyShortcutTextUnion& newKeys = Shortcut(), const std::wstring& targetAppName = L"");
|
||||
static ShortcutControl& AddNewShortcutControlRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, const Shortcut& originalKeys = Shortcut(), const KeyShortcutTextUnion& newKeys = Shortcut(), const std::wstring& targetAppName = L"");
|
||||
|
||||
// Function to delete the shortcut control
|
||||
static void ShortcutControl::DeleteShortcutControl(StackPanel& parent, std::vector<std::vector<std::unique_ptr<ShortcutControl>>>& keyboardRemapControlObjects, int index);
|
||||
|
||||
// Function to get the shortcut type
|
||||
static ShortcutType GetShortcutType(const Controls::ComboBox& typeCombo);
|
||||
|
||||
// Function to remove extra quotes from the start and end of the string (used where we will add them as needed later)
|
||||
static std::wstring ShortcutControl::RemoveExtraQuotes(const std::wstring& str);
|
||||
|
||||
// Function to return the stack panel element of the ShortcutControl. This is the externally visible UI element which can be used to add it to other layouts
|
||||
StackPanel GetShortcutControl();
|
||||
@ -65,3 +89,9 @@ public:
|
||||
// Function to create the detect shortcut UI window
|
||||
static void CreateDetectShortcutWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KBMEditor::KeyboardManagerState& keyboardManagerState, const int colIndex, StackPanel table, std::vector<std::unique_ptr<KeyDropDownControl>>& keyDropDownControlObjects, StackPanel controlLayout, TextBox targetApp, bool isHybridControl, bool isSingleKeyWindow, HWND parentWindow, RemapBuffer& remapBuffer);
|
||||
};
|
||||
|
||||
StackPanel SetupRunProgramControls(StackPanel& parent, StackPanel& row, Shortcut& shortCut, winrt::Windows::UI::Xaml::Thickness& textInputMargin, ::StackPanel& _controlStackPanel);
|
||||
|
||||
void CreateNewTempShortcut(StackPanel& row, Shortcut& tempShortcut, const uint32_t& rowIndex);
|
||||
|
||||
StackPanel SetupOpenURIControls(StackPanel& parent, StackPanel& row, Shortcut& shortCut, winrt::Windows::UI::Xaml::Thickness& textInputMargin, ::StackPanel& _controlStackPanel);
|
||||
|
@ -3,6 +3,9 @@
|
||||
|
||||
#include <common/monitor_utils.h>
|
||||
|
||||
using namespace Windows::UI::Xaml::Media;
|
||||
using namespace Windows::UI::Xaml::Automation::Peers;
|
||||
|
||||
namespace UIHelpers
|
||||
{
|
||||
// This method sets focus to the first Type button on the last row of the Grid
|
||||
@ -19,9 +22,11 @@ namespace UIHelpers
|
||||
|
||||
// Get Type Button from the first line
|
||||
Button typeButton = firstLineIntoColumn.Children().GetAt(1).as<Button>();
|
||||
|
||||
// Set programmatic focus on the button
|
||||
typeButton.Focus(FocusState::Programmatic);
|
||||
if (typeButton != nullptr)
|
||||
{
|
||||
// Set programmatic focus on the button
|
||||
typeButton.Focus(FocusState::Programmatic);
|
||||
}
|
||||
}
|
||||
|
||||
RECT GetForegroundWindowDesktopRect()
|
||||
@ -53,6 +58,34 @@ namespace UIHelpers
|
||||
return parentElement.Children().GetAt(index + 1);
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable GetLabelWrapped(const winrt::Windows::Foundation::IInspectable& element, std::wstring label, double textWidth, HorizontalAlignment horizontalAlignment)
|
||||
{
|
||||
StackPanel sp = StackPanel();
|
||||
|
||||
try
|
||||
{
|
||||
sp.Name(L"Wrapped_" + element.as<FrameworkElement>().Name());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
sp.Orientation(Orientation::Horizontal);
|
||||
sp.HorizontalAlignment(horizontalAlignment);
|
||||
TextBlock text;
|
||||
text.FontWeight(Text::FontWeights::Bold());
|
||||
text.Text(label);
|
||||
|
||||
if (textWidth >= 0)
|
||||
{
|
||||
text.Width(textWidth);
|
||||
}
|
||||
|
||||
sp.Children().Append(text);
|
||||
sp.Children().Append(element.as<FrameworkElement>());
|
||||
return sp;
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable GetWrapped(const winrt::Windows::Foundation::IInspectable& element, double width)
|
||||
{
|
||||
StackPanel sp = StackPanel();
|
||||
|
@ -27,6 +27,9 @@ namespace UIHelpers
|
||||
|
||||
winrt::Windows::Foundation::IInspectable GetWrapped(const winrt::Windows::Foundation::IInspectable& element, double width);
|
||||
|
||||
// Function to return a StackPanel with an element and a TextBlock label.
|
||||
winrt::Windows::Foundation::IInspectable GetLabelWrapped(const winrt::Windows::Foundation::IInspectable& element, std::wstring label, double width, HorizontalAlignment horizontalAlignment = HorizontalAlignment::Left);
|
||||
|
||||
// Function to return the list of key name in the order for the drop down based on the key codes
|
||||
winrt::Windows::Foundation::Collections::IVector<winrt::Windows::Foundation::IInspectable> ToBoxValue(const std::vector<std::pair<DWORD, std::wstring>>& list);
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,31 @@
|
||||
#include "pch.h"
|
||||
#include <shellapi.h>
|
||||
#include "KeyboardEventHandlers.h"
|
||||
|
||||
#include <common/interop/shared_constants.h>
|
||||
#include <common/utils/elevation.h>
|
||||
|
||||
#include <keyboardmanager/common/InputInterface.h>
|
||||
#include <keyboardmanager/common/Helpers.h>
|
||||
#include <keyboardmanager/KeyboardManagerEngineLibrary/trace.h>
|
||||
|
||||
#include <TlHelp32.h>
|
||||
#include <thread>
|
||||
#include <future>
|
||||
#include <chrono>
|
||||
|
||||
#include <winrt/Windows.UI.Notifications.h>
|
||||
#include <winrt/Windows.Data.Xml.Dom.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <string>
|
||||
#include <urlmon.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::UI::Notifications;
|
||||
using namespace Windows::Data::Xml::Dom;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool GeneratedByKBM(const LowlevelKeyboardEvent* data)
|
||||
@ -203,6 +222,8 @@ namespace KeyboardEventHandlers
|
||||
// Function to a handle a shortcut remap
|
||||
intptr_t HandleShortcutRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state, const std::optional<std::wstring>& activatedApp) noexcept
|
||||
{
|
||||
auto resetChordsResults = ResetChordsIfNeeded(data, state, activatedApp);
|
||||
|
||||
// Check if any shortcut is currently in the invoked state
|
||||
bool isShortcutInvoked = state.CheckShortcutRemapInvoked(activatedApp);
|
||||
|
||||
@ -224,15 +245,53 @@ namespace KeyboardEventHandlers
|
||||
const bool remapToKey = it->second.targetShortcut.index() == 0;
|
||||
const bool remapToShortcut = it->second.targetShortcut.index() == 1;
|
||||
const bool remapToText = it->second.targetShortcut.index() == 2;
|
||||
|
||||
const bool isRunProgram = (remapToShortcut && std::get<Shortcut>(it->second.targetShortcut).IsRunProgram());
|
||||
const bool isOpenUri = (remapToShortcut && std::get<Shortcut>(it->second.targetShortcut).IsOpenURI());
|
||||
const size_t src_size = it->first.Size();
|
||||
const size_t dest_size = remapToShortcut ? std::get<Shortcut>(it->second.targetShortcut).Size() : 1;
|
||||
|
||||
bool isMatchOnChordEnd = false;
|
||||
bool isMatchOnChordStart = false;
|
||||
|
||||
// If the shortcut has been pressed down
|
||||
if (!it->second.isShortcutInvoked && it->first.CheckModifiersKeyboardState(ii))
|
||||
{
|
||||
if (data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))
|
||||
// if not a mod key, check for chord stuff
|
||||
if (!resetChordsResults.CurrentKeyIsModifierKey && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))
|
||||
{
|
||||
if (itShortcut.HasChord())
|
||||
{
|
||||
if (!resetChordsResults.AnyChordStarted && data->lParam->vkCode == itShortcut.GetActionKey() && !itShortcut.IsChordStarted() && itShortcut.HasChord())
|
||||
{
|
||||
// start new chord
|
||||
// Logger::trace(L"ChordKeyboardHandler:new chord started for {}", data->lParam->vkCode);
|
||||
isMatchOnChordStart = true;
|
||||
ResetAllOtherStartedChords(state, activatedApp, data->lParam->vkCode);
|
||||
itShortcut.SetChordStarted(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data->lParam->vkCode == itShortcut.GetSecondKey() && itShortcut.IsChordStarted() && itShortcut.HasChord())
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:found chord match {}, {}", itShortcut.GetActionKey(), itShortcut.GetSecondKey());
|
||||
isMatchOnChordEnd = true;
|
||||
}
|
||||
|
||||
if (resetChordsResults.AnyChordStarted && !isMatchOnChordEnd)
|
||||
{
|
||||
// Logger::trace(L"ChordKeyboardHandler:waiting on second key of chord, checked {} for {}", itShortcut.GetSecondKey(), data->lParam->vkCode);
|
||||
// this is a key and there is a mod, but it's not the second key of a chord.
|
||||
// we can't do anything with this key, we're waiting.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isMatchOnChordEnd || (!resetChordsResults.AnyChordStarted && !itShortcut.HasChord() && (data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))))
|
||||
{
|
||||
ResetAllStartedChords(state, activatedApp);
|
||||
resetChordsResults.AnyChordStarted = false;
|
||||
|
||||
// Check if any other keys have been pressed apart from the shortcut. If true, then check for the next shortcut. This is to be done only for shortcut to shortcut remaps
|
||||
if (!it->first.IsKeyboardStateClearExceptShortcut(ii) && (remapToShortcut || (remapToKey && std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)))
|
||||
{
|
||||
@ -252,9 +311,75 @@ namespace KeyboardEventHandlers
|
||||
it->second.winKeyInvoked = ModifierKey::Left;
|
||||
}
|
||||
|
||||
if (remapToShortcut)
|
||||
if (isRunProgram)
|
||||
{
|
||||
// Get the common keys between the two shortcuts
|
||||
auto threadFunction = [it]() {
|
||||
CreateOrShowProcessForShortcut(std::get<Shortcut>(it->second.targetShortcut));
|
||||
};
|
||||
|
||||
std::thread myThread(threadFunction);
|
||||
if (myThread.joinable())
|
||||
{
|
||||
myThread.detach();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:returning..");
|
||||
return 1;
|
||||
}
|
||||
else if (isOpenUri)
|
||||
{
|
||||
auto shortcut = std::get<Shortcut>(it->second.targetShortcut);
|
||||
|
||||
auto uri = shortcut.uriToOpen;
|
||||
auto newUri = uri;
|
||||
|
||||
if (!PathIsURL(uri.c_str()))
|
||||
{
|
||||
WCHAR url[1024];
|
||||
DWORD bufferSize = 1024;
|
||||
|
||||
if (UrlCreateFromPathW(uri.c_str(), url, &bufferSize, 0) == S_OK)
|
||||
{
|
||||
newUri = url;
|
||||
Logger::trace(L"ChordKeyboardHandler:ConvertPathToURI from {} to {}", uri, url);
|
||||
}
|
||||
else
|
||||
{
|
||||
// need access to text resources, maybe "convert-resx-to-rc.ps1" is not working to get
|
||||
// text from KeyboardManagerEditor to here in KeyboardManagerEngineLibrary land?
|
||||
toast(L"Error", L"Could not understand the Path or URI");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
auto threadFunction = [newUri]() {
|
||||
HINSTANCE result = ShellExecute(NULL, L"open", newUri.c_str(), NULL, NULL, SW_SHOWNORMAL);
|
||||
|
||||
if (result == reinterpret_cast<HINSTANCE>(HINSTANCE_ERROR))
|
||||
{
|
||||
// need access to text resources, maybe "convert-resx-to-rc.ps1" is not working to get
|
||||
// text from KeyboardManagerEditor to here in KeyboardManagerEngineLibrary land?
|
||||
toast(L"Error", L"Could not understand the Path or URI");
|
||||
}
|
||||
};
|
||||
|
||||
std::thread myThread(threadFunction);
|
||||
if (myThread.joinable())
|
||||
{
|
||||
myThread.detach();
|
||||
}
|
||||
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:returning..");
|
||||
return 1;
|
||||
}
|
||||
else if (remapToShortcut)
|
||||
{
|
||||
// Get the common keys between the two shortcuts if this is not a runProgram shortcut
|
||||
|
||||
int commonKeys = it->first.GetCommonModifiersCount(std::get<Shortcut>(it->second.targetShortcut));
|
||||
|
||||
// If the original shortcut modifiers are a subset of the new shortcut
|
||||
@ -367,6 +492,8 @@ namespace KeyboardEventHandlers
|
||||
state.SetActivatedApp(*activatedApp);
|
||||
}
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:key_count:{}", key_count);
|
||||
|
||||
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
|
||||
delete[] keyEventList;
|
||||
|
||||
@ -383,7 +510,7 @@ namespace KeyboardEventHandlers
|
||||
dayWeLastSentAppSpecificShortcutToKeyTelemetryOn = currentDay;
|
||||
}
|
||||
}
|
||||
else if (remapToShortcut)
|
||||
else if (remapToShortcut && (!isRunProgram) && (!isOpenUri))
|
||||
{
|
||||
static int dayWeLastSentAppSpecificShortcutToShortcutTelemetryOn = -1;
|
||||
auto currentDay = std::chrono::duration_cast<std::chrono::days>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
@ -406,7 +533,7 @@ namespace KeyboardEventHandlers
|
||||
dayWeLastSentShortcutToKeyTelemetryOn = currentDay;
|
||||
}
|
||||
}
|
||||
else if (remapToShortcut)
|
||||
else if (remapToShortcut && (!isRunProgram) && (!isOpenUri))
|
||||
{
|
||||
static int dayWeLastSentShortcutToShortcutTelemetryOn = -1;
|
||||
auto currentDay = std::chrono::duration_cast<std::chrono::days>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
@ -433,7 +560,7 @@ namespace KeyboardEventHandlers
|
||||
// 6. The user releases any key apart from original modifier or original action key - This can't happen since the key down would have to happen first, which is handled above
|
||||
|
||||
// Get the common keys between the two shortcuts
|
||||
int commonKeys = remapToShortcut ? it->first.GetCommonModifiersCount(std::get<Shortcut>(it->second.targetShortcut)) : 0;
|
||||
int commonKeys = (remapToShortcut && !isRunProgram) ? it->first.GetCommonModifiersCount(std::get<Shortcut>(it->second.targetShortcut)) : 0;
|
||||
|
||||
// Case 1: If any of the modifier keys of the original shortcut are released before the action key
|
||||
if ((it->first.CheckWinKey(data->lParam->vkCode) || it->first.CheckCtrlKey(data->lParam->vkCode) || it->first.CheckAltKey(data->lParam->vkCode) || it->first.CheckShiftKey(data->lParam->vkCode)) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
|
||||
@ -441,7 +568,7 @@ namespace KeyboardEventHandlers
|
||||
// Release new shortcut, and set original shortcut keys except the one released
|
||||
size_t key_count = 0;
|
||||
LPINPUT keyEventList = nullptr;
|
||||
if (remapToShortcut)
|
||||
if (remapToShortcut && !isRunProgram)
|
||||
{
|
||||
// if the released key is present in both shortcuts' modifiers (i.e part of the common modifiers)
|
||||
if (std::get<Shortcut>(it->second.targetShortcut).CheckWinKey(data->lParam->vkCode) || std::get<Shortcut>(it->second.targetShortcut).CheckCtrlKey(data->lParam->vkCode) || std::get<Shortcut>(it->second.targetShortcut).CheckAltKey(data->lParam->vkCode) || std::get<Shortcut>(it->second.targetShortcut).CheckShiftKey(data->lParam->vkCode))
|
||||
@ -472,6 +599,7 @@ namespace KeyboardEventHandlers
|
||||
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
}
|
||||
|
||||
Helpers::SetModifierKeyEvents(std::get<Shortcut>(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first, data->lParam->vkCode);
|
||||
|
||||
// Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message
|
||||
@ -892,6 +1020,667 @@ namespace KeyboardEventHandlers
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::wstring URL_encode(const std::wstring& filepath)
|
||||
{
|
||||
std::wostringstream escaped;
|
||||
escaped.fill('0');
|
||||
escaped << std::hex;
|
||||
|
||||
for (wchar_t ch : filepath)
|
||||
{
|
||||
// Encode special characters except for colon after drive letter
|
||||
if (!iswalnum(ch) && ch != L'-' && ch != L'_' && ch != L'.' && ch != L'~' && !(ch == L':' && std::isalpha(filepath[0])))
|
||||
{
|
||||
escaped << std::uppercase;
|
||||
//escaped << '%' << std::setw(2) << int((unsigned char)ch);
|
||||
escaped << '%' << std::setw(2) << static_cast<int>((static_cast<unsigned char>(ch)));
|
||||
escaped << std::nouppercase;
|
||||
}
|
||||
else
|
||||
{
|
||||
escaped << ch;
|
||||
}
|
||||
}
|
||||
|
||||
return escaped.str();
|
||||
}
|
||||
|
||||
std::wstring ConvertPathToURI(const std::wstring& filePath)
|
||||
{
|
||||
std::wstring fileUri = std::filesystem::absolute(filePath).wstring();
|
||||
std::replace(fileUri.begin(), fileUri.end(), L'\\', L'/');
|
||||
fileUri = L"file:///" + URL_encode(fileUri);
|
||||
|
||||
return fileUri;
|
||||
}
|
||||
|
||||
void ResetAllOtherStartedChords(State& state, const std::optional<std::wstring>& activatedApp, DWORD keyToKeep)
|
||||
{
|
||||
for (auto& itShortcut_2 : state.GetSortedShortcutRemapVector(activatedApp))
|
||||
{
|
||||
if (keyToKeep == NULL || itShortcut_2.actionKey != keyToKeep)
|
||||
{
|
||||
itShortcut_2.SetChordStarted(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResetAllStartedChords(State& state, const std::optional<std::wstring>& activatedApp)
|
||||
{
|
||||
ResetAllOtherStartedChords(state, activatedApp, NULL);
|
||||
}
|
||||
|
||||
ResetChordsResults ResetChordsIfNeeded(LowlevelKeyboardEvent* data, State& state, const std::optional<std::wstring>& activatedApp)
|
||||
{
|
||||
ResetChordsResults result;
|
||||
result.AnyChordStarted = false;
|
||||
result.CurrentKeyIsModifierKey = false;
|
||||
|
||||
bool isNewControlKey = false;
|
||||
bool anyChordStarted = false;
|
||||
if (VK_LWIN == data->lParam->vkCode || VK_RWIN == data->lParam->vkCode)
|
||||
{
|
||||
isNewControlKey = true;
|
||||
}
|
||||
if (VK_LSHIFT == data->lParam->vkCode || VK_RSHIFT == data->lParam->vkCode)
|
||||
{
|
||||
isNewControlKey = true;
|
||||
}
|
||||
if (VK_LMENU == data->lParam->vkCode || VK_RMENU == data->lParam->vkCode)
|
||||
{
|
||||
isNewControlKey = true;
|
||||
}
|
||||
if (VK_LCONTROL == data->lParam->vkCode || VK_RCONTROL == data->lParam->vkCode)
|
||||
{
|
||||
isNewControlKey = true;
|
||||
}
|
||||
|
||||
if (isNewControlKey)
|
||||
{
|
||||
//Logger::trace(L"ChordKeyboardHandler:reset");
|
||||
|
||||
for (auto& itShortcut : state.GetSortedShortcutRemapVector(activatedApp))
|
||||
{
|
||||
itShortcut.SetChordStarted(false);
|
||||
}
|
||||
result.CurrentKeyIsModifierKey = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& itShortcut : state.GetSortedShortcutRemapVector(activatedApp))
|
||||
{
|
||||
if (itShortcut.IsChordStarted())
|
||||
{
|
||||
result.AnyChordStarted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct handle_data
|
||||
{
|
||||
unsigned long process_id;
|
||||
HWND window_handle;
|
||||
};
|
||||
|
||||
// used for reactivating a window for a program we already started.
|
||||
HWND FindMainWindow(unsigned long process_id, const bool allowNonVisible)
|
||||
{
|
||||
handle_data data;
|
||||
data.process_id = process_id;
|
||||
data.window_handle = 0;
|
||||
|
||||
if (allowNonVisible)
|
||||
{
|
||||
EnumWindows(EnumWindowsCallbackAllowNonVisible, reinterpret_cast<LPARAM>(&data));
|
||||
}
|
||||
else
|
||||
{
|
||||
EnumWindows(EnumWindowsCallback, reinterpret_cast<LPARAM>(&data));
|
||||
}
|
||||
|
||||
return data.window_handle;
|
||||
}
|
||||
|
||||
// used by FindMainWindow
|
||||
BOOL CALLBACK EnumWindowsCallbackAllowNonVisible(HWND handle, LPARAM lParam)
|
||||
{
|
||||
handle_data& data = *reinterpret_cast<handle_data*>(lParam);
|
||||
unsigned long process_id = 0;
|
||||
GetWindowThreadProcessId(handle, &process_id);
|
||||
|
||||
if (data.process_id == process_id)
|
||||
{
|
||||
data.window_handle = handle;
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// used by FindMainWindow
|
||||
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam)
|
||||
{
|
||||
handle_data& data = *reinterpret_cast<handle_data*>(lParam);
|
||||
unsigned long process_id = 0;
|
||||
GetWindowThreadProcessId(handle, &process_id);
|
||||
|
||||
if (data.process_id != process_id || !(GetWindow(handle, GW_OWNER) == static_cast<HWND>(0) && IsWindowVisible(handle)))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
data.window_handle = handle;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// GetProcessIdByName also used by HandleCreateProcessHotKeysAndChords
|
||||
|
||||
std::vector<DWORD> GetProcessesIdByName(const std::wstring& processName)
|
||||
{
|
||||
std::vector<DWORD> processIds;
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
|
||||
if (snapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
PROCESSENTRY32 processEntry;
|
||||
processEntry.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (Process32First(snapshot, &processEntry))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (_wcsicmp(processEntry.szExeFile, processName.c_str()) == 0)
|
||||
{
|
||||
processIds.push_back(processEntry.th32ProcessID);
|
||||
}
|
||||
} while (Process32Next(snapshot, &processEntry));
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
|
||||
return processIds;
|
||||
}
|
||||
|
||||
DWORD GetProcessIdByName(const std::wstring& processName)
|
||||
{
|
||||
DWORD pid = 0;
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
|
||||
if (snapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
PROCESSENTRY32 processEntry;
|
||||
processEntry.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (Process32First(snapshot, &processEntry))
|
||||
{
|
||||
do
|
||||
{
|
||||
if (_wcsicmp(processEntry.szExeFile, processName.c_str()) == 0)
|
||||
{
|
||||
pid = processEntry.th32ProcessID;
|
||||
break;
|
||||
}
|
||||
} while (Process32Next(snapshot, &processEntry));
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
// Use to find a process by its name
|
||||
std::wstring GetFileNameFromPath(const std::wstring& fullPath)
|
||||
{
|
||||
size_t found = fullPath.find_last_of(L"\\");
|
||||
if (found != std::wstring::npos)
|
||||
{
|
||||
return fullPath.substr(found + 1);
|
||||
}
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
void toast(param::hstring const& message1, param::hstring const& message2) noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
// Alternatively can build DOM from code:
|
||||
XmlDocument toastXml;
|
||||
XmlElement toastElement = toastXml.CreateElement(L"toast");
|
||||
XmlElement visualElement = toastXml.CreateElement(L"visual");
|
||||
XmlElement bindingElement = toastXml.CreateElement(L"binding");
|
||||
XmlElement textElement1 = toastXml.CreateElement(L"text");
|
||||
XmlElement textElement2 = toastXml.CreateElement(L"text");
|
||||
|
||||
toastXml.AppendChild(toastElement);
|
||||
toastElement.AppendChild(visualElement);
|
||||
visualElement.AppendChild(bindingElement);
|
||||
|
||||
bindingElement.AppendChild(textElement1);
|
||||
bindingElement.AppendChild(textElement2);
|
||||
|
||||
bindingElement.SetAttribute(L"template", L"ToastGeneric");
|
||||
|
||||
textElement1.InnerText(message1);
|
||||
textElement2.InnerText(message2);
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:toastXml {}", toastXml.GetXml());
|
||||
std::wstring APPLICATION_ID = L"Microsoft.PowerToysWin32";
|
||||
const auto notifier = ToastNotificationManager::ToastNotificationManager::CreateToastNotifier(APPLICATION_ID);
|
||||
|
||||
ToastNotification notification{ toastXml };
|
||||
notifier.Show(notification);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
/*std::thread{ [message] {
|
||||
|
||||
} }.detach();*/
|
||||
}
|
||||
|
||||
void CreateOrShowProcessForShortcut(Shortcut shortcut) noexcept
|
||||
{
|
||||
WCHAR fullExpandedFilePath[MAX_PATH];
|
||||
DWORD result = ExpandEnvironmentStrings(shortcut.runProgramFilePath.c_str(), fullExpandedFilePath, MAX_PATH);
|
||||
|
||||
auto fileNamePart = GetFileNameFromPath(fullExpandedFilePath);
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, trying to run {}", fileNamePart, fullExpandedFilePath);
|
||||
//lastKeyInChord = 0;
|
||||
|
||||
DWORD targetPid = GetProcessIdByName(fileNamePart);
|
||||
|
||||
/*if (fileNamePart != L"explorer.exe" && fileNamePart != L"powershell.exe" && fileNamePart != L"cmd.exe" && fileNamePart != L"msedge.exe")
|
||||
{
|
||||
targetPid = GetProcessIdByName(fileNamePart);
|
||||
}*/
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, already running, pid:{}, alreadyRunningAction:{}", fileNamePart, targetPid, shortcut.alreadyRunningAction);
|
||||
|
||||
if (targetPid != 0 && shortcut.alreadyRunningAction != Shortcut::ProgramAlreadyRunningAction::StartAnother)
|
||||
{
|
||||
if (shortcut.alreadyRunningAction == Shortcut::ProgramAlreadyRunningAction::EndTask)
|
||||
{
|
||||
TerminateProcessesByName(fileNamePart);
|
||||
return;
|
||||
}
|
||||
else if (shortcut.alreadyRunningAction == Shortcut::ProgramAlreadyRunningAction::Close)
|
||||
{
|
||||
CloseProcessByName(fileNamePart);
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, CloseProcessByName returning 3", fileNamePart);
|
||||
return;
|
||||
}
|
||||
else if (shortcut.alreadyRunningAction == Shortcut::ProgramAlreadyRunningAction::ShowWindow)
|
||||
{
|
||||
auto processIds = GetProcessesIdByName(fileNamePart);
|
||||
|
||||
for (DWORD pid : processIds)
|
||||
{
|
||||
ShowProgram(targetPid, fileNamePart, false, false, 0);
|
||||
}
|
||||
|
||||
//if (!ShowProgram(targetPid, fileNamePart, false, false, 0))
|
||||
//{
|
||||
// /*auto future = std::async(std::launch::async, [=] {
|
||||
// std::this_thread::sleep_for(std::chrono::milliseconds(30));
|
||||
// Logger::trace(L"ChordKeyboardHandler:{}, second try, pid:{}", fileNamePart, targetPid);
|
||||
// ShowProgram(targetPid, fileNamePart, false, false);
|
||||
//});*/
|
||||
//}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD dwAttrib = GetFileAttributesW(fullExpandedFilePath);
|
||||
|
||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
std::wstring title = fmt::format(L"Error starting {}", fileNamePart);
|
||||
std::wstring message = fmt::format(L"The program was not found.");
|
||||
toast(title, message);
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring executable_and_args = fmt::format(L"\"{}\" {}", fullExpandedFilePath, shortcut.runProgramArgs);
|
||||
|
||||
WCHAR currentDir[MAX_PATH];
|
||||
WCHAR* currentDirPtr = currentDir;
|
||||
DWORD result = ExpandEnvironmentStrings(shortcut.runProgramStartInDir.c_str(), currentDir, MAX_PATH);
|
||||
|
||||
if (shortcut.runProgramStartInDir == L"")
|
||||
{
|
||||
currentDirPtr = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD dwAttrib = GetFileAttributesW(currentDir);
|
||||
|
||||
if (dwAttrib == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
std::wstring title = fmt::format(L"Error starting {}", fileNamePart);
|
||||
std::wstring message = fmt::format(L"The start in path was not valid. It could not be used.", currentDir);
|
||||
currentDirPtr = nullptr;
|
||||
toast(title, message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD processId = 0;
|
||||
HANDLE newProcessHandle;
|
||||
|
||||
if (shortcut.elevationLevel == Shortcut::ElevationLevel::Elevated)
|
||||
{
|
||||
newProcessHandle = run_elevated(fullExpandedFilePath, shortcut.runProgramArgs, currentDirPtr, (shortcut.startWindowType == Shortcut::StartWindowType::Normal));
|
||||
processId = GetProcessId(newProcessHandle);
|
||||
}
|
||||
else if (shortcut.elevationLevel == Shortcut::ElevationLevel::NonElevated)
|
||||
{
|
||||
run_non_elevated(fullExpandedFilePath, shortcut.runProgramArgs, &processId, currentDirPtr, (shortcut.startWindowType == Shortcut::StartWindowType::Normal));
|
||||
}
|
||||
else if (shortcut.elevationLevel == Shortcut::ElevationLevel::DifferentUser)
|
||||
{
|
||||
newProcessHandle = run_as_different_user(fullExpandedFilePath, shortcut.runProgramArgs, currentDirPtr, (shortcut.startWindowType == Shortcut::StartWindowType::Normal));
|
||||
processId = GetProcessId(newProcessHandle);
|
||||
}
|
||||
|
||||
if (processId == 0)
|
||||
{
|
||||
std::wstring title = fmt::format(L"Error starting {}", fileNamePart);
|
||||
std::wstring message = fmt::format(L"The application might not have started.");
|
||||
toast(title, message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shortcut.startWindowType == Shortcut::StartWindowType::Hidden)
|
||||
{
|
||||
HideProgram(processId, fileNamePart, 0);
|
||||
}
|
||||
//ShowProgram(processId, fileNamePart, true, false, (shortcut.startWindowType == Shortcut::StartWindowType::Hidden), 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void CloseProcessByName(const std::wstring& fileNamePart)
|
||||
{
|
||||
auto processIds = GetProcessesIdByName(fileNamePart);
|
||||
|
||||
if (processIds.size() == 0)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, Nothing To WM_CLOSE", fileNamePart);
|
||||
return;
|
||||
}
|
||||
|
||||
auto threadFunction = [fileNamePart]() {
|
||||
auto processIds = GetProcessesIdByName(fileNamePart);
|
||||
auto retryCount = 10;
|
||||
while (processIds.size() > 0 && retryCount-- > 0)
|
||||
{
|
||||
//Logger::trace(L"ChordKeyboardHandler:{}, WM_CLOSE 'ing {}processIds ", fileNamePart, processIds.size());
|
||||
for (DWORD pid : processIds)
|
||||
{
|
||||
//Logger::trace(L"ChordKeyboardHandler:{}, WM_CLOSE ({}) -> pid:{}", fileNamePart, retryCount, pid);
|
||||
HWND hwnd = FindMainWindow(pid, false);
|
||||
SendMessage(hwnd, WM_CLOSE, 0, 0);
|
||||
|
||||
// small sleep between when there are a lot might help
|
||||
Sleep(10);
|
||||
}
|
||||
|
||||
processIds = GetProcessesIdByName(fileNamePart);
|
||||
if (processIds.size() <= 0)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, WM_CLOSE done", fileNamePart);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
processIds = GetProcessesIdByName(fileNamePart);
|
||||
|
||||
if (processIds.size() > 0)
|
||||
{
|
||||
std::thread myThread(threadFunction);
|
||||
if (myThread.joinable())
|
||||
{
|
||||
myThread.detach();
|
||||
}
|
||||
}
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, CloseProcessByName returning", fileNamePart);
|
||||
}
|
||||
|
||||
void TerminateProcessesByName(const std::wstring& fileNamePart)
|
||||
{
|
||||
auto processIds = GetProcessesIdByName(fileNamePart);
|
||||
|
||||
if (processIds.size() == 0)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, Nothing To PROCESS_TERMINATE", fileNamePart);
|
||||
return;
|
||||
}
|
||||
|
||||
for (DWORD pid : processIds)
|
||||
{
|
||||
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, PROCESS_TERMINATE (1) -> pid:{}", fileNamePart, pid);
|
||||
if (hProcess != NULL)
|
||||
{
|
||||
if (!TerminateProcess(hProcess, 0))
|
||||
{
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HideProgram(DWORD pid, std::wstring programName, int retryCount)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:HideProgram starting with {},{}, retryCount:{}", pid, programName, retryCount);
|
||||
|
||||
HWND hwnd = FindMainWindow(pid, false);
|
||||
if (hwnd == NULL)
|
||||
{
|
||||
if (retryCount < 20)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:hwnd not found will retry for pid:{}", pid);
|
||||
auto future = std::async(std::launch::async, [=] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
auto result = HideProgram(pid, programName, retryCount + 1);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
hwnd = FindWindow(nullptr, nullptr);
|
||||
|
||||
auto anyHideResultFailed = false;
|
||||
|
||||
Logger::trace(L"ChordKeyboardHandler:{}:{},{}, FindWindow, HideProgram (all)", programName, pid, retryCount);
|
||||
while (hwnd)
|
||||
{
|
||||
DWORD pidForHwnd;
|
||||
GetWindowThreadProcessId(hwnd, &pidForHwnd);
|
||||
if (pid == pidForHwnd)
|
||||
{
|
||||
if (IsWindowVisible(hwnd))
|
||||
{
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, tryToHide {}, {}", programName, reinterpret_cast<uintptr_t>(hwnd), anyHideResultFailed);
|
||||
}
|
||||
}
|
||||
hwnd = FindWindowEx(NULL, hwnd, NULL, NULL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShowProgram(DWORD pid, std::wstring programName, bool isNewProcess, bool minimizeIfVisible, int retryCount)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:ShowProgram starting with {},{},isNewProcess:{}, tryToHide:{} retryCount:{}", pid, programName, isNewProcess, retryCount);
|
||||
|
||||
// a good place to look for this...
|
||||
// https://github.com/ritchielawrence/cmdow
|
||||
|
||||
// try by main window.
|
||||
auto allowNonVisible = false;
|
||||
|
||||
HWND hwnd = FindMainWindow(pid, allowNonVisible);
|
||||
|
||||
if (hwnd == NULL)
|
||||
{
|
||||
if (retryCount < 20)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:hwnd not found will retry for pid:{}, allowNonVisible:{}", pid, allowNonVisible);
|
||||
|
||||
auto future = std::async(std::launch::async, [=] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
auto result = ShowProgram(pid, programName, isNewProcess, minimizeIfVisible, retryCount + 1);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, got hwnd from FindMainWindow", programName);
|
||||
|
||||
if (hwnd == GetForegroundWindow())
|
||||
{
|
||||
// only hide if this was a call from a already open program, don't make small if we just opened it.
|
||||
if (!isNewProcess && minimizeIfVisible)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, got GetForegroundWindow, doing SW_MINIMIZE", programName);
|
||||
return ShowWindow(hwnd, SW_MINIMIZE);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, not ForegroundWindow, doing SW_RESTORE", programName);
|
||||
|
||||
// Check if the window is minimized
|
||||
if (IsIconic(hwnd))
|
||||
{
|
||||
// Show the window since SetForegroundWindow fails on minimized windows
|
||||
if (!ShowWindow(hwnd, SW_RESTORE))
|
||||
{
|
||||
Logger::error(L"ShowWindow failed");
|
||||
}
|
||||
}
|
||||
|
||||
INPUT inputs[1] = { { .type = INPUT_MOUSE } };
|
||||
SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));
|
||||
|
||||
if (!SetForegroundWindow(hwnd))
|
||||
{
|
||||
auto errorCode = GetLastError();
|
||||
Logger::warn(L"ChordKeyboardHandler:{}, failed to SetForegroundWindow, {}", programName, errorCode);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, success on SetForegroundWindow", programName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNewProcess)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (false)
|
||||
{
|
||||
// try by console.
|
||||
hwnd = FindWindow(nullptr, nullptr);
|
||||
if (AttachConsole(pid))
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, success on AttachConsole", programName);
|
||||
|
||||
// Get the console window handle
|
||||
hwnd = GetConsoleWindow();
|
||||
auto showByConsoleSuccess = false;
|
||||
if (hwnd != NULL)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, success on GetConsoleWindow, doing SW_RESTORE", programName);
|
||||
|
||||
ShowWindow(hwnd, SW_RESTORE);
|
||||
|
||||
if (!SetForegroundWindow(hwnd))
|
||||
{
|
||||
auto errorCode = GetLastError();
|
||||
Logger::warn(L"ChordKeyboardHandler:{}, failed to SetForegroundWindow, {}", programName, errorCode);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, success on SetForegroundWindow", programName);
|
||||
showByConsoleSuccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Detach from the console
|
||||
FreeConsole();
|
||||
if (showByConsoleSuccess)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// try to just show them all (if they have a title)!.
|
||||
hwnd = FindWindow(nullptr, nullptr);
|
||||
|
||||
auto anyHideResultFailed = false;
|
||||
if (hwnd)
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}:{},{}, FindWindow (show all mode)", programName, pid, retryCount);
|
||||
while (hwnd)
|
||||
{
|
||||
DWORD pidForHwnd;
|
||||
GetWindowThreadProcessId(hwnd, &pidForHwnd);
|
||||
if (pid == pidForHwnd)
|
||||
{
|
||||
int length = GetWindowTextLength(hwnd);
|
||||
|
||||
if (length > 0)
|
||||
{
|
||||
ShowWindow(hwnd, SW_RESTORE);
|
||||
|
||||
// hwnd is the window handle with targetPid
|
||||
if (SetForegroundWindow(hwnd))
|
||||
{
|
||||
Logger::trace(L"ChordKeyboardHandler:{}, success on SetForegroundWindow", programName);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto errorCode = GetLastError();
|
||||
Logger::warn(L"ChordKeyboardHandler:{}, failed to SetForegroundWindow, {}", programName, errorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
hwnd = FindWindowEx(NULL, hwnd, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function to a handle an os-level shortcut remap
|
||||
intptr_t HandleOSLevelShortcutRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state) noexcept
|
||||
{
|
||||
|
@ -10,6 +10,13 @@ namespace KeyboardManagerInput
|
||||
|
||||
namespace KeyboardEventHandlers
|
||||
{
|
||||
|
||||
struct ResetChordsResults
|
||||
{
|
||||
bool CurrentKeyIsModifierKey;
|
||||
bool AnyChordStarted;
|
||||
};
|
||||
|
||||
// Function to a handle a single key remap
|
||||
intptr_t HandleSingleKeyRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state) noexcept;
|
||||
|
||||
@ -21,6 +28,51 @@ namespace KeyboardEventHandlers
|
||||
// Function to a handle a shortcut remap
|
||||
intptr_t HandleShortcutRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state, const std::optional<std::wstring>& activatedApp = std::nullopt) noexcept;
|
||||
|
||||
// Function to reset chord matching
|
||||
void ResetAllStartedChords(State& state, const std::optional<std::wstring>& activatedApp);
|
||||
|
||||
// Function to reset chord matching
|
||||
void ResetAllOtherStartedChords(State& state, const std::optional<std::wstring>& activatedApp, DWORD keyToKeep);
|
||||
|
||||
std::wstring URL_encode(const std::wstring& value);
|
||||
|
||||
std::wstring ConvertPathToURI(const std::wstring& filePath);
|
||||
|
||||
// Function to reset chord matching if needed
|
||||
ResetChordsResults ResetChordsIfNeeded(LowlevelKeyboardEvent* data, State& state, const std::optional<std::wstring>& activatedApp);
|
||||
|
||||
// Function to handle (start or show) programs for shortcuts
|
||||
void CreateOrShowProcessForShortcut(Shortcut shortcut) noexcept;
|
||||
|
||||
void CloseProcessByName(const std::wstring& fileNamePart);
|
||||
|
||||
void TerminateProcessesByName(const std::wstring& fileNamePart);
|
||||
|
||||
void toast(winrt::param::hstring const& message1, winrt::param::hstring const& message2) noexcept;
|
||||
|
||||
// Function to help FindMainWindow
|
||||
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam);
|
||||
|
||||
// Function to help FindMainWindow
|
||||
BOOL CALLBACK EnumWindowsCallbackAllowNonVisible(HWND handle, LPARAM lParam);
|
||||
|
||||
// Function to FindMainWindow
|
||||
HWND FindMainWindow(unsigned long process_id, const bool allowNonVisible);
|
||||
|
||||
// Function to GetProcessIdByName
|
||||
DWORD GetProcessIdByName(const std::wstring& processName);
|
||||
|
||||
// Function to GetProcessesIdByName
|
||||
std::vector<DWORD> GetProcessesIdByName(const std::wstring& processName);
|
||||
|
||||
// Function to get just the file name from a fill path
|
||||
std::wstring GetFileNameFromPath(const std::wstring& fullPath);
|
||||
|
||||
// Function to a find and show a running program
|
||||
bool ShowProgram(DWORD pid, std::wstring programName, bool isNewProcess, bool minimizeIfVisible, int retryCount);
|
||||
|
||||
bool HideProgram(DWORD pid, std::wstring programName, int retryCount);
|
||||
|
||||
// Function to a handle an os-level shortcut remap
|
||||
intptr_t HandleOSLevelShortcutRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state) noexcept;
|
||||
|
||||
|
@ -7,8 +7,6 @@
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <common/logger/logger_settings.h>
|
||||
|
||||
#include <keyboardmanager/common/Shortcut.h>
|
||||
#include <keyboardmanager/common/RemapShortcut.h>
|
||||
#include <keyboardmanager/common/KeyboardManagerConstants.h>
|
||||
#include <keyboardmanager/common/Helpers.h>
|
||||
#include <keyboardmanager/common/KeyboardEventHandlers.h>
|
||||
|
@ -11,7 +11,7 @@ public:
|
||||
|
||||
// Constructor
|
||||
KeyboardManager();
|
||||
|
||||
|
||||
~KeyboardManager()
|
||||
{
|
||||
if (editorIsRunningEvent)
|
||||
@ -19,7 +19,7 @@ public:
|
||||
CloseHandle(editorIsRunningEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void StartLowlevelKeyboardHook();
|
||||
void StopLowlevelKeyboardHook();
|
||||
|
||||
|
@ -59,13 +59,14 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" />
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.ImplementationLibrary.1.0.231216.1\build\native\Microsoft.Windows.ImplementationLibrary.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
|
||||
<package id="Microsoft.Windows.ImplementationLibrary" version="1.0.231216.1" targetFramework="native" />
|
||||
</packages>
|
@ -3,6 +3,7 @@
|
||||
#include <windows.h>
|
||||
#include <ProjectTelemetry.h>
|
||||
#include <shlwapi.h>
|
||||
#include <shellapi.h>
|
||||
#include <stdexcept>
|
||||
#include <unordered_set>
|
||||
#include <winrt/base.h>
|
||||
|
@ -166,6 +166,10 @@ std::wstring GetShortcutHumanReadableString(Shortcut const & shortcut, LayoutMap
|
||||
if (shortcut.actionKey != NULL)
|
||||
{
|
||||
humanReadableShortcut += keyboardMap.GetKeyName(shortcut.actionKey);
|
||||
if (shortcut.secondKey != NULL)
|
||||
{
|
||||
humanReadableShortcut += L" , " + keyboardMap.GetKeyName(shortcut.secondKey);
|
||||
}
|
||||
}
|
||||
return humanReadableShortcut;
|
||||
}
|
||||
@ -227,6 +231,8 @@ void Trace::SendKeyAndShortcutRemapLoadedConfiguration(State& remappings) noexce
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.ctrlKey), "ModifierRemapFromCtrl"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.altKey), "ModifierRemapFromAlt"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.shiftKey), "ModifierRemapFromShift"),
|
||||
TraceLoggingBool(shortcutRemappedFrom.HasChord(), "KeyRemapFromHasChord"),
|
||||
TraceLoggingInt64(shortcutRemappedFrom.secondKey, "KeyRemapFromChordSecondKey"),
|
||||
TraceLoggingInt64(keyRemappedTo, "KeyRemapTo"),
|
||||
TraceLoggingWideString(GetShortcutHumanReadableString(shortcutRemappedFrom, keyboardMap).c_str(), "HumanRemapFrom"),
|
||||
TraceLoggingWideString(keyboardMap.GetKeyName(keyRemappedTo).c_str(), "HumanRemapTo"));
|
||||
@ -234,6 +240,11 @@ void Trace::SendKeyAndShortcutRemapLoadedConfiguration(State& remappings) noexce
|
||||
else if (shortcutRemap.second.targetShortcut.index() == 1) // 1 - Remapping to shortcut
|
||||
{
|
||||
Shortcut shortcutRemappedTo = std::get<Shortcut>(shortcutRemap.second.targetShortcut);
|
||||
if (shortcutRemappedTo.IsRunProgram() || shortcutRemappedTo.IsOpenURI())
|
||||
{
|
||||
// Don't include Start app or Open URI mappings in this telemetry.
|
||||
continue;
|
||||
}
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"KeyboardManager_ShortcutRemapConfigurationLoaded",
|
||||
@ -244,6 +255,8 @@ void Trace::SendKeyAndShortcutRemapLoadedConfiguration(State& remappings) noexce
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.ctrlKey), "ModifierRemapFromCtrl"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.altKey), "ModifierRemapFromAlt"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.shiftKey), "ModifierRemapFromShift"),
|
||||
TraceLoggingBool(shortcutRemappedFrom.HasChord(), "KeyRemapFromHasChord"),
|
||||
TraceLoggingInt64(shortcutRemappedFrom.secondKey, "KeyRemapFromChordSecondKey"),
|
||||
TraceLoggingInt64(shortcutRemappedTo.actionKey, "KeyRemapTo"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedTo.winKey), "ModifierRemapToWin"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedTo.ctrlKey), "ModifierRemapToCtrl"),
|
||||
@ -274,6 +287,8 @@ void Trace::SendKeyAndShortcutRemapLoadedConfiguration(State& remappings) noexce
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.ctrlKey), "ModifierRemapFromCtrl"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.altKey), "ModifierRemapFromAlt"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.shiftKey), "ModifierRemapFromShift"),
|
||||
TraceLoggingBool(shortcutRemappedFrom.HasChord(), "KeyRemapFromHasChord"),
|
||||
TraceLoggingInt64(shortcutRemappedFrom.secondKey, "KeyRemapFromChordSecondKey"),
|
||||
TraceLoggingInt64(keyRemappedTo, "KeyRemapTo"),
|
||||
TraceLoggingWideString(GetShortcutHumanReadableString(shortcutRemappedFrom, keyboardMap).c_str(), "HumanRemapFrom"),
|
||||
TraceLoggingWideString(keyboardMap.GetKeyName(keyRemappedTo).c_str(), "HumanRemapTo"),
|
||||
@ -283,6 +298,11 @@ void Trace::SendKeyAndShortcutRemapLoadedConfiguration(State& remappings) noexce
|
||||
else if (shortcutRemap.second.targetShortcut.index() == 1) // 1 - Remapping to shortcut
|
||||
{
|
||||
Shortcut shortcutRemappedTo = std::get<Shortcut>(shortcutRemap.second.targetShortcut);
|
||||
if (shortcutRemappedTo.IsRunProgram() || shortcutRemappedTo.IsOpenURI())
|
||||
{
|
||||
// Don't include Start app or Open URI mappings in this telemetry.
|
||||
continue;
|
||||
}
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"KeyboardManager_AppSpecificShortcutRemapConfigurationLoaded",
|
||||
@ -293,6 +313,8 @@ void Trace::SendKeyAndShortcutRemapLoadedConfiguration(State& remappings) noexce
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.ctrlKey), "ModifierRemapFromCtrl"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.altKey), "ModifierRemapFromAlt"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedFrom.shiftKey), "ModifierRemapFromShift"),
|
||||
TraceLoggingBool(shortcutRemappedFrom.HasChord(), "KeyRemapFromHasChord"),
|
||||
TraceLoggingInt64(shortcutRemappedFrom.secondKey, "KeyRemapFromChordSecondKey"),
|
||||
TraceLoggingInt64(shortcutRemappedTo.actionKey, "KeyRemapTo"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedTo.winKey), "ModifierRemapToWin"),
|
||||
TraceLoggingInt8(static_cast<INT8>(shortcutRemappedTo.ctrlKey), "ModifierRemapToCtrl"),
|
||||
|
@ -35,6 +35,7 @@
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
|
@ -28,6 +28,9 @@ namespace KeyboardManagerConstants
|
||||
// Name of the property use to store shortcut to text remaps.
|
||||
inline const std::wstring RemapShortcutsToTextSettingName = L"remapShortcutsToText";
|
||||
|
||||
// Name of the property use to store shortcut to run-program remaps.
|
||||
inline const std::wstring RemapShortcutsToRunProgramSettingName = L"remapShortcutsToRunProgram";
|
||||
|
||||
// Name of the property use to store global shortcut remaps array.
|
||||
inline const std::wstring GlobalRemapShortcutsSettingName = L"global";
|
||||
|
||||
@ -43,6 +46,33 @@ namespace KeyboardManagerConstants
|
||||
// Name of the property use to store new remapped string.
|
||||
inline const std::wstring NewTextSettingName = L"unicodeText";
|
||||
|
||||
// Name of the property use to store runProgramStartInDir.
|
||||
inline const std::wstring RunProgramStartInDirSettingName = L"runProgramStartInDir";
|
||||
|
||||
// Name of the property use to store runProgramStartInDir.
|
||||
inline const std::wstring RunProgramElevationLevelSettingName = L"runProgramElevationLevel";
|
||||
|
||||
// Name of the property use to store runProgramAlreadyRunningAction.
|
||||
inline const std::wstring RunProgramAlreadyRunningAction = L"runProgramAlreadyRunningAction";
|
||||
|
||||
// Name of the property use to store runProgramStartWindowType.
|
||||
inline const std::wstring RunProgramStartWindowType = L"runProgramStartWindowType";
|
||||
|
||||
// Name of the property use to store runProgramArgs.
|
||||
inline const std::wstring RunProgramArgsSettingName = L"runProgramArgs";
|
||||
|
||||
// Name of the property use to store runProgramFilePath.
|
||||
inline const std::wstring RunProgramFilePathSettingName = L"runProgramFilePath";
|
||||
|
||||
// Name of the property use to store secondKeyOfChord.
|
||||
inline const std::wstring ShortcutSecondKeyOfChordSettingName = L"secondKeyOfChord";
|
||||
|
||||
// Name of the property use to store openUri.
|
||||
inline const std::wstring ShortcutOpenURI = L"openUri";
|
||||
|
||||
// Name of the property use to store shortcutOperationType.
|
||||
inline const std::wstring ShortcutOperationType = L"operationType";
|
||||
|
||||
// Name of the property use to store the target application.
|
||||
inline const std::wstring TargetAppSettingName = L"targetApp";
|
||||
|
||||
|
@ -179,6 +179,13 @@ bool MappingConfiguration::LoadSingleKeyToTextRemaps(const json::JsonObject& jso
|
||||
{
|
||||
auto originalKey = it.GetObjectW().GetNamedString(KeyboardManagerConstants::OriginalKeysSettingName);
|
||||
auto newText = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewTextSettingName);
|
||||
|
||||
// undo dummy data for backwards compatibility
|
||||
if (newText == L"*Unsupported*")
|
||||
{
|
||||
newText == L"";
|
||||
}
|
||||
|
||||
AddSingleKeyToTextRemap(std::stoul(originalKey.c_str()), newText.c_str());
|
||||
}
|
||||
catch (...)
|
||||
@ -212,6 +219,45 @@ bool MappingConfiguration::LoadAppSpecificShortcutRemaps(const json::JsonObject&
|
||||
auto newRemapKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewRemapKeysSettingName, {});
|
||||
auto newRemapText = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewTextSettingName, {});
|
||||
auto targetApp = it.GetObjectW().GetNamedString(KeyboardManagerConstants::TargetAppSettingName);
|
||||
auto operationType = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::ShortcutOperationType, 0);
|
||||
|
||||
// undo dummy data for backwards compatibility
|
||||
if (newRemapText == L"*Unsupported*")
|
||||
{
|
||||
newRemapText == L"";
|
||||
}
|
||||
|
||||
// check Shortcut::OperationType
|
||||
if (operationType == 1)
|
||||
{
|
||||
auto runProgramFilePath = it.GetObjectW().GetNamedString(KeyboardManagerConstants::RunProgramFilePathSettingName, L"");
|
||||
auto runProgramArgs = it.GetObjectW().GetNamedString(KeyboardManagerConstants::RunProgramArgsSettingName, L"");
|
||||
auto runProgramStartInDir = it.GetObjectW().GetNamedString(KeyboardManagerConstants::RunProgramStartInDirSettingName, L"");
|
||||
auto runProgramElevationLevel = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::RunProgramElevationLevelSettingName, 0);
|
||||
auto runProgramAlreadyRunningAction = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::RunProgramAlreadyRunningAction, 0);
|
||||
auto runProgramStartWindowType = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::RunProgramStartWindowType, 0);
|
||||
auto secondKeyOfChord = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, 0);
|
||||
|
||||
auto tempShortcut = Shortcut(newRemapKeys.c_str());
|
||||
tempShortcut.operationType = Shortcut::OperationType::RunProgram;
|
||||
tempShortcut.runProgramFilePath = runProgramFilePath;
|
||||
tempShortcut.runProgramArgs = runProgramArgs;
|
||||
tempShortcut.runProgramStartInDir = runProgramStartInDir;
|
||||
tempShortcut.elevationLevel = static_cast<Shortcut::ElevationLevel>(runProgramElevationLevel);
|
||||
tempShortcut.alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(runProgramAlreadyRunningAction);
|
||||
tempShortcut.startWindowType = static_cast<Shortcut::StartWindowType>(runProgramStartWindowType);
|
||||
|
||||
AddAppSpecificShortcut(targetApp.c_str(), Shortcut(originalKeys.c_str(), static_cast<DWORD>(secondKeyOfChord)), tempShortcut);
|
||||
}
|
||||
else if (operationType == 2)
|
||||
{
|
||||
auto secondKeyOfChord = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, 0);
|
||||
auto tempShortcut = Shortcut(newRemapKeys.c_str());
|
||||
tempShortcut.operationType = Shortcut::OperationType::OpenURI;
|
||||
tempShortcut.uriToOpen = it.GetObjectW().GetNamedString(KeyboardManagerConstants::ShortcutOpenURI, L"");
|
||||
|
||||
AddAppSpecificShortcut(targetApp.c_str(), Shortcut(originalKeys.c_str(), static_cast<DWORD>(secondKeyOfChord)), tempShortcut);
|
||||
}
|
||||
|
||||
if (!newRemapKeys.empty())
|
||||
{
|
||||
@ -268,8 +314,47 @@ bool MappingConfiguration::LoadShortcutRemaps(const json::JsonObject& jsonData,
|
||||
auto originalKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::OriginalKeysSettingName);
|
||||
auto newRemapKeys = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewRemapKeysSettingName, {});
|
||||
auto newRemapText = it.GetObjectW().GetNamedString(KeyboardManagerConstants::NewTextSettingName, {});
|
||||
auto operationType = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::ShortcutOperationType, 0);
|
||||
|
||||
if (!newRemapKeys.empty())
|
||||
// undo dummy data for backwards compatibility
|
||||
if (newRemapText == L"*Unsupported*")
|
||||
{
|
||||
newRemapText == L"";
|
||||
}
|
||||
|
||||
// check Shortcut::OperationType
|
||||
if (operationType == 1)
|
||||
{
|
||||
auto runProgramFilePath = it.GetObjectW().GetNamedString(KeyboardManagerConstants::RunProgramFilePathSettingName, L"");
|
||||
auto runProgramArgs = it.GetObjectW().GetNamedString(KeyboardManagerConstants::RunProgramArgsSettingName, L"");
|
||||
auto runProgramStartInDir = it.GetObjectW().GetNamedString(KeyboardManagerConstants::RunProgramStartInDirSettingName, L"");
|
||||
auto runProgramElevationLevel = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::RunProgramElevationLevelSettingName, 0);
|
||||
auto runProgramStartWindowType = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::RunProgramStartWindowType, 0);
|
||||
auto secondKeyOfChord = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, 0);
|
||||
|
||||
auto runProgramAlreadyRunningAction = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::RunProgramAlreadyRunningAction, 0);
|
||||
|
||||
auto tempShortcut = Shortcut(newRemapKeys.c_str());
|
||||
tempShortcut.operationType = Shortcut::OperationType::RunProgram;
|
||||
tempShortcut.runProgramFilePath = runProgramFilePath;
|
||||
tempShortcut.runProgramArgs = runProgramArgs;
|
||||
tempShortcut.runProgramStartInDir = runProgramStartInDir;
|
||||
tempShortcut.elevationLevel = static_cast<Shortcut::ElevationLevel>(runProgramElevationLevel);
|
||||
tempShortcut.alreadyRunningAction = static_cast<Shortcut::ProgramAlreadyRunningAction>(runProgramAlreadyRunningAction);
|
||||
tempShortcut.startWindowType = static_cast<Shortcut::StartWindowType>(runProgramStartWindowType);
|
||||
|
||||
AddOSLevelShortcut(Shortcut(originalKeys.c_str(), static_cast<DWORD>(secondKeyOfChord)), tempShortcut);
|
||||
}
|
||||
else if (operationType == 2)
|
||||
{
|
||||
auto secondKeyOfChord = it.GetObjectW().GetNamedNumber(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, 0);
|
||||
auto tempShortcut = Shortcut(newRemapKeys.c_str());
|
||||
tempShortcut.operationType = Shortcut::OperationType::OpenURI;
|
||||
tempShortcut.uriToOpen = it.GetObjectW().GetNamedString(KeyboardManagerConstants::ShortcutOpenURI, L"");
|
||||
|
||||
AddOSLevelShortcut(Shortcut(originalKeys.c_str(), static_cast<DWORD>(secondKeyOfChord)), tempShortcut);
|
||||
}
|
||||
else if (!newRemapKeys.empty())
|
||||
{
|
||||
// If remapped to a shortcut
|
||||
if (std::wstring(newRemapKeys).find(L";") != std::string::npos)
|
||||
@ -416,13 +501,48 @@ bool MappingConfiguration::SaveSettingsToFile()
|
||||
// For shortcut to shortcut remapping
|
||||
else if (it.second.targetShortcut.index() == 1)
|
||||
{
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewRemapKeysSettingName, json::value(std::get<Shortcut>(it.second.targetShortcut).ToHstringVK()));
|
||||
auto targetShortcut = std::get<Shortcut>(it.second.targetShortcut);
|
||||
|
||||
if (targetShortcut.operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramElevationLevelSettingName, json::value(static_cast<unsigned int>(targetShortcut.elevationLevel)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, json::value(static_cast<unsigned int>(it.first.secondKey)));
|
||||
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutOperationType, json::value(static_cast<unsigned int>(targetShortcut.operationType)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramAlreadyRunningAction, json::value(static_cast<unsigned int>(targetShortcut.alreadyRunningAction)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramStartWindowType, json::value(static_cast<unsigned int>(targetShortcut.startWindowType)));
|
||||
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramFilePathSettingName, json::value(targetShortcut.runProgramFilePath));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramArgsSettingName, json::value(targetShortcut.runProgramArgs));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramStartInDirSettingName, json::value(targetShortcut.runProgramStartInDir));
|
||||
|
||||
// we need to add this dummy data for backwards compatibility
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewTextSettingName, json::value(L"*Unsupported*"));
|
||||
}
|
||||
else if (targetShortcut.operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramElevationLevelSettingName, json::value(static_cast<unsigned int>(targetShortcut.elevationLevel)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, json::value(static_cast<unsigned int>(it.first.secondKey)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutOperationType, json::value(static_cast<unsigned int>(targetShortcut.operationType)));
|
||||
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutOpenURI, json::value(targetShortcut.uriToOpen));
|
||||
|
||||
// we need to add this dummy data for backwards compatibility
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewTextSettingName, json::value(L"*Unsupported*"));
|
||||
}
|
||||
else
|
||||
{
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutOperationType, json::value(static_cast<unsigned int>(targetShortcut.operationType)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, json::value(static_cast<unsigned int>(it.first.secondKey)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewRemapKeysSettingName, json::value(targetShortcut.ToHstringVK()));
|
||||
}
|
||||
}
|
||||
// For shortcut to text remapping
|
||||
else if (it.second.targetShortcut.index() == 2)
|
||||
{
|
||||
remapToText = true;
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewTextSettingName, json::value(std::get<std::wstring>(it.second.targetShortcut)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, json::value(static_cast<unsigned int>(it.first.secondKey)));
|
||||
}
|
||||
|
||||
if (!remapToText)
|
||||
@ -450,7 +570,40 @@ bool MappingConfiguration::SaveSettingsToFile()
|
||||
// For shortcut to shortcut remapping
|
||||
else if (itKeys.second.targetShortcut.index() == 1)
|
||||
{
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewRemapKeysSettingName, json::value(std::get<Shortcut>(itKeys.second.targetShortcut).ToHstringVK()));
|
||||
auto targetShortcut = std::get<Shortcut>(itKeys.second.targetShortcut);
|
||||
|
||||
if (targetShortcut.operationType == Shortcut::OperationType::RunProgram)
|
||||
{
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramElevationLevelSettingName, json::value(static_cast<unsigned int>(targetShortcut.elevationLevel)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, json::value(static_cast<unsigned int>(itKeys.first.secondKey)));
|
||||
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutOperationType, json::value(static_cast<unsigned int>(targetShortcut.operationType)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramAlreadyRunningAction, json::value(static_cast<unsigned int>(targetShortcut.alreadyRunningAction)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramStartWindowType, json::value(static_cast<unsigned int>(targetShortcut.startWindowType)));
|
||||
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramFilePathSettingName, json::value(targetShortcut.runProgramFilePath));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramArgsSettingName, json::value(targetShortcut.runProgramArgs));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramStartInDirSettingName, json::value(targetShortcut.runProgramStartInDir));
|
||||
|
||||
// we need to add this dummy data for backwards compatibility
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewTextSettingName, json::value(L"*Unsupported*"));
|
||||
}
|
||||
else if (targetShortcut.operationType == Shortcut::OperationType::OpenURI)
|
||||
{
|
||||
keys.SetNamedValue(KeyboardManagerConstants::RunProgramElevationLevelSettingName, json::value(static_cast<unsigned int>(targetShortcut.elevationLevel)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutSecondKeyOfChordSettingName, json::value(static_cast<unsigned int>(itKeys.first.secondKey)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutOperationType, json::value(static_cast<unsigned int>(targetShortcut.operationType)));
|
||||
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutOpenURI, json::value(targetShortcut.uriToOpen));
|
||||
|
||||
// we need to add this dummy data for backwards compatibility
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewTextSettingName, json::value(L"*Unsupported*"));
|
||||
}
|
||||
else
|
||||
{
|
||||
keys.SetNamedValue(KeyboardManagerConstants::ShortcutOperationType, json::value(static_cast<unsigned int>(targetShortcut.operationType)));
|
||||
keys.SetNamedValue(KeyboardManagerConstants::NewRemapKeysSettingName, json::value(std::get<Shortcut>(itKeys.second.targetShortcut).ToHstringVK()));
|
||||
}
|
||||
}
|
||||
else if (itKeys.second.targetShortcut.index() == 2)
|
||||
{
|
||||
|
@ -26,11 +26,33 @@ Shortcut::Shortcut(const std::wstring& shortcutVK) :
|
||||
winKey(ModifierKey::Disabled), ctrlKey(ModifierKey::Disabled), altKey(ModifierKey::Disabled), shiftKey(ModifierKey::Disabled), actionKey(NULL)
|
||||
{
|
||||
auto keys = splitwstring(shortcutVK, ';');
|
||||
SetKeyCodes(ConvertToNumbers(keys));
|
||||
}
|
||||
|
||||
std::vector<int32_t> Shortcut::ConvertToNumbers(std::vector<std::wstring>& keys)
|
||||
{
|
||||
std::vector<int32_t> keysAsNumbers;
|
||||
for (auto it : keys)
|
||||
{
|
||||
auto vkKeyCode = std::stoul(it);
|
||||
SetKey(vkKeyCode);
|
||||
keysAsNumbers.push_back(vkKeyCode);
|
||||
}
|
||||
return keysAsNumbers;
|
||||
}
|
||||
|
||||
// Constructor to initialize Shortcut from single key
|
||||
Shortcut::Shortcut(const DWORD key)
|
||||
{
|
||||
SetKey(key);
|
||||
}
|
||||
|
||||
// Constructor to initialize Shortcut from it's virtual key code string representation.
|
||||
Shortcut::Shortcut(const std::wstring& shortcutVK, const DWORD secondKeyOfChord) :
|
||||
winKey(ModifierKey::Disabled), ctrlKey(ModifierKey::Disabled), altKey(ModifierKey::Disabled), shiftKey(ModifierKey::Disabled), actionKey(NULL)
|
||||
{
|
||||
auto keys = splitwstring(shortcutVK, ';');
|
||||
SetKeyCodes(ConvertToNumbers(keys));
|
||||
secondKey = secondKeyOfChord;
|
||||
}
|
||||
|
||||
// Constructor to initialize shortcut from a list of keys
|
||||
@ -88,6 +110,8 @@ void Shortcut::Reset()
|
||||
altKey = ModifierKey::Disabled;
|
||||
shiftKey = ModifierKey::Disabled;
|
||||
actionKey = NULL;
|
||||
secondKey = NULL;
|
||||
chordStarted = false;
|
||||
}
|
||||
|
||||
// Function to return the action key
|
||||
@ -96,6 +120,36 @@ DWORD Shortcut::GetActionKey() const
|
||||
return actionKey;
|
||||
}
|
||||
|
||||
bool Shortcut::IsRunProgram() const
|
||||
{
|
||||
return operationType == OperationType::RunProgram;
|
||||
}
|
||||
|
||||
bool Shortcut::IsOpenURI() const
|
||||
{
|
||||
return operationType == OperationType::OpenURI;
|
||||
}
|
||||
|
||||
DWORD Shortcut::GetSecondKey() const
|
||||
{
|
||||
return secondKey;
|
||||
}
|
||||
|
||||
bool Shortcut::HasChord() const
|
||||
{
|
||||
return secondKey != NULL;
|
||||
}
|
||||
|
||||
void Shortcut::SetChordStarted(bool started)
|
||||
{
|
||||
chordStarted = started;
|
||||
}
|
||||
|
||||
bool Shortcut::IsChordStarted() const
|
||||
{
|
||||
return chordStarted;
|
||||
}
|
||||
|
||||
// Function to return the virtual key code of the win key state expected in the shortcut. Argument is used to decide which win key to return in case of both. If the current shortcut doesn't use both win keys then arg is ignored. Return NULL if it is not a part of the shortcut
|
||||
DWORD Shortcut::GetWinKey(const ModifierKey& input) const
|
||||
{
|
||||
@ -281,6 +335,16 @@ bool Shortcut::CheckShiftKey(const DWORD input) const
|
||||
}
|
||||
}
|
||||
|
||||
bool Shortcut::SetSecondKey(const DWORD input)
|
||||
{
|
||||
if (secondKey == input)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
secondKey = input;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Function to set a key in the shortcut based on the passed key code argument. Returns false if it is already set to the same value. This can be used to avoid UI refreshing
|
||||
bool Shortcut::SetKey(const DWORD input)
|
||||
{
|
||||
@ -413,10 +477,10 @@ void Shortcut::ResetKey(const DWORD input)
|
||||
{
|
||||
shiftKey = ModifierKey::Disabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
actionKey = {};
|
||||
}
|
||||
|
||||
// we always want to reset these also, I think for now since this got a little weirder when chords
|
||||
actionKey = {};
|
||||
secondKey = {};
|
||||
}
|
||||
|
||||
// Function to return the string representation of the shortcut in virtual key codes appended in a string by ";" separator.
|
||||
@ -444,6 +508,11 @@ winrt::hstring Shortcut::ToHstringVK() const
|
||||
output = output + winrt::to_hstring(static_cast<unsigned int>(GetActionKey())) + winrt::to_hstring(L";");
|
||||
}
|
||||
|
||||
if (secondKey != NULL)
|
||||
{
|
||||
output = output + winrt::to_hstring(static_cast<unsigned int>(GetSecondKey())) + winrt::to_hstring(L";");
|
||||
}
|
||||
|
||||
if (!output.empty())
|
||||
{
|
||||
output = winrt::hstring(output.c_str(), output.size() - 1);
|
||||
@ -479,15 +548,48 @@ std::vector<DWORD> Shortcut::GetKeyCodes()
|
||||
return keys;
|
||||
}
|
||||
|
||||
bool Shortcut::IsActionKey(const DWORD input)
|
||||
{
|
||||
auto shortcut = Shortcut();
|
||||
shortcut.SetKey(input);
|
||||
return (shortcut.actionKey != NULL);
|
||||
}
|
||||
|
||||
bool Shortcut::IsModifier(const DWORD input)
|
||||
{
|
||||
auto shortcut = Shortcut();
|
||||
shortcut.SetKey(input);
|
||||
return (shortcut.actionKey == NULL);
|
||||
}
|
||||
|
||||
// Function to set a shortcut from a vector of key codes
|
||||
void Shortcut::SetKeyCodes(const std::vector<int32_t>& keys)
|
||||
{
|
||||
Reset();
|
||||
|
||||
bool foundActionKey = false;
|
||||
for (int i = 0; i < keys.size(); i++)
|
||||
{
|
||||
if (keys[i] != -1 && keys[i] != 0)
|
||||
{
|
||||
SetKey(keys[i]);
|
||||
Shortcut tempShortcut = Shortcut(keys[i]);
|
||||
|
||||
if (!foundActionKey && tempShortcut.actionKey != NULL)
|
||||
{
|
||||
// last key was an action key, next key is secondKey
|
||||
foundActionKey = true;
|
||||
SetKey(keys[i]);
|
||||
}
|
||||
else if (foundActionKey && tempShortcut.actionKey != NULL)
|
||||
{
|
||||
// already found actionKey, and we found another, add this as the secondKey
|
||||
secondKey = keys[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
// just add whatever it is.
|
||||
SetKey(keys[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,21 +19,75 @@ private:
|
||||
|
||||
inline auto comparator() const
|
||||
{
|
||||
return std::make_tuple(winKey, ctrlKey, altKey, shiftKey, actionKey);
|
||||
return std::make_tuple(winKey, ctrlKey, altKey, shiftKey, actionKey, secondKey);
|
||||
}
|
||||
|
||||
// helper to use the same code in more places.
|
||||
std::vector<int32_t> Shortcut::ConvertToNumbers(std::vector<std::wstring>& keys);
|
||||
|
||||
public:
|
||||
enum ElevationLevel
|
||||
{
|
||||
NonElevated = 0,
|
||||
Elevated = 1,
|
||||
DifferentUser = 2
|
||||
};
|
||||
|
||||
enum OperationType
|
||||
{
|
||||
RemapShortcut = 0,
|
||||
RunProgram = 1,
|
||||
OpenURI = 2
|
||||
};
|
||||
|
||||
enum StartWindowType
|
||||
{
|
||||
Normal = 0,
|
||||
Hidden = 1,
|
||||
Minimized = 2,
|
||||
Maximized = 2
|
||||
};
|
||||
|
||||
enum ProgramAlreadyRunningAction
|
||||
{
|
||||
ShowWindow = 0,
|
||||
StartAnother = 1,
|
||||
DoNothing = 2,
|
||||
Close = 3,
|
||||
EndTask = 4,
|
||||
CloseAndEndTask = 5,
|
||||
};
|
||||
|
||||
ModifierKey winKey = ModifierKey::Disabled;
|
||||
ModifierKey ctrlKey = ModifierKey::Disabled;
|
||||
ModifierKey altKey = ModifierKey::Disabled;
|
||||
ModifierKey shiftKey = ModifierKey::Disabled;
|
||||
|
||||
std::wstring runProgramFilePath;
|
||||
std::wstring runProgramArgs;
|
||||
std::wstring runProgramStartInDir;
|
||||
std::wstring uriToOpen;
|
||||
|
||||
ProgramAlreadyRunningAction alreadyRunningAction = ProgramAlreadyRunningAction::ShowWindow;
|
||||
ElevationLevel elevationLevel = ElevationLevel::NonElevated;
|
||||
OperationType operationType = OperationType::RemapShortcut;
|
||||
StartWindowType startWindowType = StartWindowType::Normal;
|
||||
|
||||
DWORD actionKey = {};
|
||||
DWORD secondKey = {}; // of the chord
|
||||
bool chordStarted = false;
|
||||
|
||||
Shortcut() = default;
|
||||
|
||||
// Constructor to initialize Shortcut from single key
|
||||
Shortcut(const DWORD key);
|
||||
|
||||
// Constructor to initialize Shortcut from it's virtual key code string representation.
|
||||
Shortcut(const std::wstring& shortcutVK);
|
||||
|
||||
// Constructor to initialize Shortcut from it's virtual key code string representation.
|
||||
Shortcut(const std::wstring& shortcutVK, const DWORD _secondKeyOfChord);
|
||||
|
||||
// Constructor to initialize shortcut from a list of keys
|
||||
Shortcut(const std::vector<int32_t>& keys);
|
||||
|
||||
@ -47,6 +101,10 @@ public:
|
||||
return lhs.comparator() == rhs.comparator();
|
||||
}
|
||||
|
||||
static bool Shortcut::IsActionKey(const DWORD input);
|
||||
|
||||
static bool Shortcut::IsModifier(const DWORD input);
|
||||
|
||||
// Function to return the number of keys in the shortcut
|
||||
int Size() const;
|
||||
|
||||
@ -59,6 +117,24 @@ public:
|
||||
// Function to return the action key
|
||||
DWORD GetActionKey() const;
|
||||
|
||||
// Function to return IsRunProgram
|
||||
bool Shortcut::IsRunProgram() const;
|
||||
|
||||
// Function to return IsOpenURI
|
||||
bool Shortcut::IsOpenURI() const;
|
||||
|
||||
// Function to return the second key (of the chord)
|
||||
DWORD Shortcut::GetSecondKey() const;
|
||||
|
||||
// Function to check if a chord is started for this shortcut
|
||||
bool Shortcut::IsChordStarted() const;
|
||||
|
||||
// Function to check if this shortcut has a chord
|
||||
bool Shortcut::HasChord() const;
|
||||
|
||||
// Function to set that we started a chord
|
||||
void Shortcut::SetChordStarted(bool started);
|
||||
|
||||
// Function to return the virtual key code of the win key state expected in the shortcut. Argument is used to decide which win key to return in case of both. If the current shortcut doesn't use both win keys then arg is ignored. Return NULL if it is not a part of the shortcut
|
||||
DWORD GetWinKey(const ModifierKey& input) const;
|
||||
|
||||
@ -86,6 +162,9 @@ public:
|
||||
// Function to set a key in the shortcut based on the passed key code argument. Returns false if it is already set to the same value. This can be used to avoid UI refreshing
|
||||
bool SetKey(const DWORD input);
|
||||
|
||||
// Function to SetSecondKey
|
||||
bool SetSecondKey(const DWORD input);
|
||||
|
||||
// Function to reset the state of a shortcut key based on the passed key code argument
|
||||
void ResetKey(const DWORD input);
|
||||
|
||||
|
@ -18,9 +18,29 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
return base.GetMappedOriginalKeys();
|
||||
}
|
||||
|
||||
public new List<string> GetMappedNewRemapKeys()
|
||||
public new List<string> GetMappedOriginalKeysWithSplitChord()
|
||||
{
|
||||
return base.GetMappedNewRemapKeys();
|
||||
return base.GetMappedOriginalKeysWithSplitChord();
|
||||
}
|
||||
|
||||
public List<string> GetMappedOriginalKeys(bool ignoreSecondKeyInChord)
|
||||
{
|
||||
return base.GetMappedOriginalKeys(ignoreSecondKeyInChord);
|
||||
}
|
||||
|
||||
public List<string> GetMappedOriginalKeysWithoutChord()
|
||||
{
|
||||
return base.GetMappedOriginalKeys(true);
|
||||
}
|
||||
|
||||
public new List<string> GetMappedOriginalKeysOnlyChord()
|
||||
{
|
||||
return base.GetMappedOriginalKeysOnlyChord();
|
||||
}
|
||||
|
||||
public new List<string> GetMappedNewRemapKeys(int runProgramMaxLength)
|
||||
{
|
||||
return base.GetMappedNewRemapKeys(runProgramMaxLength);
|
||||
}
|
||||
|
||||
public bool Compare(AppSpecificKeysDataModel arg)
|
||||
|
@ -2,42 +2,312 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Windows.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class KeysDataModel
|
||||
public class KeysDataModel : INotifyPropertyChanged
|
||||
{
|
||||
[JsonPropertyName("originalKeys")]
|
||||
public string OriginalKeys { get; set; }
|
||||
|
||||
[JsonPropertyName("secondKeyOfChord")]
|
||||
public int SecondKeyOfChord { get; set; }
|
||||
|
||||
[JsonPropertyName("newRemapKeys")]
|
||||
public string NewRemapKeys { get; set; }
|
||||
|
||||
[JsonPropertyName("unicodeText")]
|
||||
public string NewRemapString { get; set; }
|
||||
|
||||
[JsonPropertyName("runProgramFilePath")]
|
||||
public string RunProgramFilePath { get; set; }
|
||||
|
||||
[JsonPropertyName("runProgramArgs")]
|
||||
public string RunProgramArgs { get; set; }
|
||||
|
||||
[JsonPropertyName("openUri")]
|
||||
public string OpenUri { get; set; }
|
||||
|
||||
[JsonPropertyName("operationType")]
|
||||
public int OperationType { get; set; }
|
||||
|
||||
private enum KeyboardManagerEditorType
|
||||
{
|
||||
KeyEditor = 0,
|
||||
ShortcutEditor,
|
||||
}
|
||||
|
||||
public const string CommaSeparator = "<comma>";
|
||||
|
||||
private static Process editor;
|
||||
private ICommand _editShortcutItemCommand;
|
||||
|
||||
public ICommand EditShortcutItem => _editShortcutItemCommand ?? (_editShortcutItemCommand = new RelayCommand<object>(OnEditShortcutItem));
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged(string propertyName)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
public void OnEditShortcutItem(object parameter)
|
||||
{
|
||||
OpenEditor((int)KeyboardManagerEditorType.ShortcutEditor);
|
||||
}
|
||||
|
||||
private async void OpenEditor(int type)
|
||||
{
|
||||
if (editor != null)
|
||||
{
|
||||
BringProcessToFront(editor);
|
||||
return;
|
||||
}
|
||||
|
||||
const string PowerToyName = KeyboardManagerSettings.ModuleName;
|
||||
const string KeyboardManagerEditorPath = "KeyboardManagerEditor\\PowerToys.KeyboardManagerEditor.exe";
|
||||
try
|
||||
{
|
||||
if (editor != null && editor.HasExited)
|
||||
{
|
||||
Logger.LogInfo($"Previous instance of {PowerToyName} editor exited");
|
||||
editor = null;
|
||||
}
|
||||
|
||||
if (editor != null)
|
||||
{
|
||||
Logger.LogInfo($"The {PowerToyName} editor instance {editor.Id} exists. Bringing the process to the front");
|
||||
BringProcessToFront(editor);
|
||||
return;
|
||||
}
|
||||
|
||||
string path = Path.Combine(Environment.CurrentDirectory, KeyboardManagerEditorPath);
|
||||
Logger.LogInfo($"Starting {PowerToyName} editor from {path}");
|
||||
|
||||
// InvariantCulture: type represents the KeyboardManagerEditorType enum value
|
||||
editor = Process.Start(path, $"{type.ToString(CultureInfo.InvariantCulture)} {Environment.ProcessId}");
|
||||
|
||||
await editor.WaitForExitAsync();
|
||||
|
||||
editor = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
editor = null;
|
||||
Logger.LogError($"Exception encountered when opening an {PowerToyName} editor", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BringProcessToFront(Process process)
|
||||
{
|
||||
if (process == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IntPtr handle = process.MainWindowHandle;
|
||||
if (NativeMethods.IsIconic(handle))
|
||||
{
|
||||
NativeMethods.ShowWindow(handle, NativeMethods.SWRESTORE);
|
||||
}
|
||||
|
||||
NativeMethods.SetForegroundWindow(handle);
|
||||
}
|
||||
|
||||
private static List<string> MapKeysOnlyChord(int secondKeyOfChord)
|
||||
{
|
||||
var result = new List<string>();
|
||||
if (secondKeyOfChord <= 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result.Add(Helper.GetKeyName((uint)secondKeyOfChord));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<string> MapKeys(string stringOfKeys, int secondKeyOfChord, bool splitChordsWithComma = false)
|
||||
{
|
||||
if (stringOfKeys == null)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
if (secondKeyOfChord > 0)
|
||||
{
|
||||
var keys = stringOfKeys.Split(';');
|
||||
return keys.Take(keys.Length - 1)
|
||||
.Select(uint.Parse)
|
||||
.Select(Helper.GetKeyName)
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (splitChordsWithComma)
|
||||
{
|
||||
var keys = stringOfKeys.Split(';')
|
||||
.Select(uint.Parse)
|
||||
.Select(Helper.GetKeyName)
|
||||
.ToList();
|
||||
keys.Insert(keys.Count - 1, CommaSeparator);
|
||||
return keys;
|
||||
}
|
||||
else
|
||||
{
|
||||
return stringOfKeys
|
||||
.Split(';')
|
||||
.Select(uint.Parse)
|
||||
.Select(Helper.GetKeyName)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> MapKeys(string stringOfKeys)
|
||||
{
|
||||
return stringOfKeys
|
||||
.Split(';')
|
||||
.Select(uint.Parse)
|
||||
.Select(Helper.GetKeyName)
|
||||
.ToList();
|
||||
return MapKeys(stringOfKeys, 0);
|
||||
}
|
||||
|
||||
public List<string> GetMappedOriginalKeys(bool ignoreSecondKeyInChord, bool splitChordsWithComma = false)
|
||||
{
|
||||
if (ignoreSecondKeyInChord && SecondKeyOfChord > 0)
|
||||
{
|
||||
return MapKeys(OriginalKeys, SecondKeyOfChord);
|
||||
}
|
||||
else
|
||||
{
|
||||
return MapKeys(OriginalKeys, -1, splitChordsWithComma);
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetMappedOriginalKeysOnlyChord()
|
||||
{
|
||||
return MapKeysOnlyChord(SecondKeyOfChord);
|
||||
}
|
||||
|
||||
public List<string> GetMappedOriginalKeys()
|
||||
{
|
||||
return MapKeys(OriginalKeys);
|
||||
return GetMappedOriginalKeys(false);
|
||||
}
|
||||
|
||||
public List<string> GetMappedNewRemapKeys()
|
||||
public List<string> GetMappedOriginalKeysWithSplitChord()
|
||||
{
|
||||
return string.IsNullOrEmpty(NewRemapString) ? MapKeys(NewRemapKeys) : new List<string> { NewRemapString };
|
||||
return GetMappedOriginalKeys(false, true);
|
||||
}
|
||||
|
||||
public bool IsRunProgram
|
||||
{
|
||||
get
|
||||
{
|
||||
return OperationType == 1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpenURI
|
||||
{
|
||||
get
|
||||
{
|
||||
return OperationType == 2;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpenUriOrIsRunProgram
|
||||
{
|
||||
get
|
||||
{
|
||||
return IsOpenURI || IsRunProgram;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasChord
|
||||
{
|
||||
get
|
||||
{
|
||||
return SecondKeyOfChord > 0;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetMappedNewRemapKeys(int runProgramMaxLength)
|
||||
{
|
||||
if (IsRunProgram)
|
||||
{
|
||||
// we're going to just pretend this is a "key" if we have a RunProgramFilePath
|
||||
if (string.IsNullOrEmpty(RunProgramFilePath))
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new List<string> { FormatFakeKeyForDisplay(runProgramMaxLength) };
|
||||
}
|
||||
}
|
||||
else if (IsOpenURI)
|
||||
{
|
||||
// we're going to just pretend this is a "key" if we have a RunProgramFilePath
|
||||
if (string.IsNullOrEmpty(OpenUri))
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OpenUri.Length > runProgramMaxLength)
|
||||
{
|
||||
return new List<string> { $"{OpenUri.Substring(0, runProgramMaxLength - 3)}..." };
|
||||
}
|
||||
else
|
||||
{
|
||||
return new List<string> { OpenUri };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (string.IsNullOrEmpty(NewRemapString) || NewRemapString == "*Unsupported*") ? MapKeys(NewRemapKeys) : new List<string> { NewRemapString };
|
||||
}
|
||||
|
||||
// Instead of doing something fancy pants, we 'll just display the RunProgramFilePath data when it's IsRunProgram
|
||||
// It truncates the start of the program to run, if it's long and truncates the end of the args if it's long
|
||||
// e.g.: c:\MyCool\PathIs\Long\software.exe myArg1 myArg2 myArg3 -> (something like) "...ng\software.exe myArg1..."
|
||||
// the idea is you get the most important part of the program to run and some of the args in case that the only thing thats different,
|
||||
// e.g: "...path\software.exe cool1.txt" and "...path\software.exe cool3.txt"
|
||||
private string FormatFakeKeyForDisplay(int runProgramMaxLength)
|
||||
{
|
||||
// was going to use this:
|
||||
var fakeKey = Environment.ExpandEnvironmentVariables(RunProgramFilePath);
|
||||
try
|
||||
{
|
||||
if (File.Exists(fakeKey))
|
||||
{
|
||||
fakeKey = Path.GetFileName(Environment.ExpandEnvironmentVariables(RunProgramFilePath));
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
fakeKey = $"{fakeKey} {RunProgramArgs}".Trim();
|
||||
|
||||
if (fakeKey.Length > runProgramMaxLength)
|
||||
{
|
||||
fakeKey = $"{fakeKey.Substring(0, runProgramMaxLength - 3)}...";
|
||||
}
|
||||
|
||||
return fakeKey;
|
||||
}
|
||||
|
||||
public string ToJsonString()
|
||||
|
@ -324,8 +324,24 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
// the settings file needs to be updated, update the real one with non-excluded stuff...
|
||||
Logger.LogInfo($"Settings file {currentFile.Key} is different and is getting updated from backup");
|
||||
|
||||
var newCurrentSettingsFile = JsonMergeHelper.Merge(File.ReadAllText(currentSettingsFiles[currentFile.Key]), settingsToRestoreJson);
|
||||
File.WriteAllText(currentSettingsFiles[currentFile.Key], newCurrentSettingsFile);
|
||||
// we needed a new "CustomRestoreSettings" for now, to overwrite because some settings don't merge well (like KBM shortcuts)
|
||||
var overwrite = false;
|
||||
if (backupRestoreSettings["CustomRestoreSettings"] != null && backupRestoreSettings["CustomRestoreSettings"][currentFile.Key] != null)
|
||||
{
|
||||
var customRestoreSettings = backupRestoreSettings["CustomRestoreSettings"][currentFile.Key];
|
||||
overwrite = customRestoreSettings["overwrite"] != null && (bool)customRestoreSettings["overwrite"];
|
||||
}
|
||||
|
||||
if (overwrite)
|
||||
{
|
||||
File.WriteAllText(currentSettingsFiles[currentFile.Key], settingsToRestoreJson);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newCurrentSettingsFile = JsonMergeHelper.Merge(File.ReadAllText(currentSettingsFiles[currentFile.Key]), settingsToRestoreJson);
|
||||
File.WriteAllText(currentSettingsFiles[currentFile.Key], newCurrentSettingsFile);
|
||||
}
|
||||
|
||||
anyFilesUpdated = true;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,11 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"CustomRestoreSettings": {
|
||||
"\\Keyboard Manager\\default.json": {
|
||||
"overwrite": true
|
||||
}
|
||||
},
|
||||
"IgnoredSettings": {
|
||||
"backup-restore_settings.json": [
|
||||
"RestartAfterRestore"
|
||||
|
@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Converters
|
||||
{
|
||||
internal sealed class KeyVisualTemplateSelector : DataTemplateSelector
|
||||
{
|
||||
public DataTemplate KeyVisualTemplate { get; set; }
|
||||
|
||||
public DataTemplate CommaTemplate { get; set; }
|
||||
|
||||
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
|
||||
{
|
||||
var stringValue = item as string;
|
||||
return stringValue == KeysDataModel.CommaSeparator ? CommaTemplate : KeyVisualTemplate;
|
||||
}
|
||||
}
|
||||
}
|
@ -15,6 +15,26 @@
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.Resources>
|
||||
<DataTemplate x:Key="KeyVisualTemplate">
|
||||
<controls:KeyVisual
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{Binding}"
|
||||
IsTabStop="False"
|
||||
VisualType="TextOnly" />
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="CommaTemplate">
|
||||
<StackPanel Background="{ThemeResource SystemFillColorSolidAttentionBackground}">
|
||||
<TextBlock
|
||||
Margin="4,0"
|
||||
VerticalAlignment="Bottom"
|
||||
Text="," />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
<converters:KeyVisualTemplateSelector
|
||||
x:Key="KeyVisualTemplateSelector"
|
||||
CommaTemplate="{StaticResource CommaTemplate}"
|
||||
KeyVisualTemplate="{StaticResource KeyVisualTemplate}" />
|
||||
<converters:ModuleItemTemplateSelector
|
||||
x:Key="ModuleItemTemplateSelector"
|
||||
ButtonTemplate="{StaticResource ModuleItemButtonTemplate}"
|
||||
@ -156,7 +176,7 @@
|
||||
<ItemsControl
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
IsTabStop="False"
|
||||
ItemsSource="{x:Bind GetMappedNewRemapKeys()}">
|
||||
ItemsSource="{x:Bind GetMappedNewRemapKeys(15)}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" />
|
||||
@ -190,7 +210,7 @@
|
||||
<DataTemplate x:DataType="Lib:AppSpecificKeysDataModel">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border
|
||||
Padding="8,4"
|
||||
Padding="8,0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
@ -198,29 +218,27 @@
|
||||
<ItemsControl
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
IsTabStop="False"
|
||||
ItemsSource="{x:Bind GetMappedOriginalKeys()}">
|
||||
ItemTemplateSelector="{StaticResource KeyVisualTemplateSelector}"
|
||||
ItemsSource="{x:Bind GetMappedOriginalKeysWithSplitChord()}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:KeyVisual
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{Binding}"
|
||||
IsTabStop="False"
|
||||
VisualType="TextOnly" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
<controls:IsEnabledTextBlock
|
||||
x:Uid="To"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SecondaryIsEnabledTextBlockStyle}" />
|
||||
Style="{StaticResource SecondaryIsEnabledTextBlockStyle}"
|
||||
Visibility="{x:Bind Path=IsOpenUriOrIsRunProgram, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
<controls:IsEnabledTextBlock
|
||||
x:Uid="Starts"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SecondaryIsEnabledTextBlockStyle}"
|
||||
Visibility="{x:Bind Path=IsOpenUriOrIsRunProgram, Mode=OneWay}" />
|
||||
<Border
|
||||
Padding="8,4"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
@ -230,7 +248,7 @@
|
||||
<ItemsControl
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
IsTabStop="False"
|
||||
ItemsSource="{x:Bind GetMappedNewRemapKeys()}">
|
||||
ItemsSource="{x:Bind GetMappedNewRemapKeys(15)}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" />
|
||||
|
@ -14,6 +14,12 @@
|
||||
|
||||
<Page.Resources>
|
||||
<tkconverters:CollectionVisibilityConverter x:Key="CollectionVisibilityConverter" />
|
||||
<tkconverters:BoolToVisibilityConverter
|
||||
x:Key="BoolToInvertedVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
<tkconverters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
|
||||
<Style x:Name="KeysListViewContainerStyle" TargetType="ListViewItem">
|
||||
<Setter Property="IsTabStop" Value="False" />
|
||||
</Style>
|
||||
@ -105,7 +111,7 @@
|
||||
x:Uid="KeyboardManager_RemappedTo"
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource RemappedKeyTemplate}"
|
||||
ItemsSource="{x:Bind GetMappedNewRemapKeys()}">
|
||||
ItemsSource="{x:Bind GetMappedNewRemapKeys(50)}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
@ -124,7 +130,7 @@
|
||||
x:Uid="KeyboardManager_RemapShortcutsButton"
|
||||
ActionIcon="{ui:FontIcon Glyph=}"
|
||||
Command="{Binding Path=EditShortcutCommand}"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsClickEnabled="True" />
|
||||
|
||||
<ListView
|
||||
@ -136,47 +142,79 @@
|
||||
Visibility="{x:Bind Path=ViewModel.RemapShortcuts, Mode=OneWay, Converter={StaticResource CollectionVisibilityConverter}}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="Lib:AppSpecificKeysDataModel">
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ItemsControl
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource OriginalKeyTemplate}"
|
||||
ItemsSource="{x:Bind GetMappedOriginalKeys()}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
<controls:IsEnabledTextBlock
|
||||
x:Uid="To"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SecondaryIsEnabledTextBlockStyle}" />
|
||||
|
||||
<ItemsControl
|
||||
Name="KeyboardManager_RemappedTo"
|
||||
x:Uid="KeyboardManager_RemappedTo"
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource RemappedKeyTemplate}"
|
||||
ItemsSource="{x:Bind GetMappedNewRemapKeys()}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
<Border
|
||||
Margin="16,0,0,0"
|
||||
Padding="12,4,12,6"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="12">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Opacity="0.3" Color="{ThemeResource SystemAccentColor}" />
|
||||
</Border.Background>
|
||||
<TextBlock Text="{x:Bind TargetApp}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard.Description>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ItemsControl
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource OriginalKeyTemplate}"
|
||||
ItemsSource="{x:Bind GetMappedOriginalKeysWithoutChord()}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
|
||||
<TextBlock
|
||||
Padding="6,0,6,6"
|
||||
VerticalAlignment="Bottom"
|
||||
Text=","
|
||||
Visibility="{x:Bind Path=HasChord, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<ItemsControl
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource OriginalKeyTemplate}"
|
||||
ItemsSource="{x:Bind GetMappedOriginalKeysOnlyChord()}"
|
||||
Visibility="{x:Bind Path=HasChord, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
|
||||
<controls:IsEnabledTextBlock
|
||||
x:Uid="To"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SecondaryIsEnabledTextBlockStyle}"
|
||||
Visibility="{x:Bind Path=IsOpenUriOrIsRunProgram, Mode=OneWay, Converter={StaticResource BoolToInvertedVisibilityConverter}}" />
|
||||
|
||||
<controls:IsEnabledTextBlock
|
||||
x:Uid="Starts"
|
||||
Margin="8,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource SecondaryIsEnabledTextBlockStyle}"
|
||||
Visibility="{x:Bind Path=IsOpenUriOrIsRunProgram, Mode=OneWay}" />
|
||||
|
||||
|
||||
<ItemsControl
|
||||
Name="KeyboardManager_RemappedTo"
|
||||
x:Uid="KeyboardManager_RemappedTo"
|
||||
IsTabStop="False"
|
||||
ItemTemplate="{StaticResource RemappedKeyTemplate}"
|
||||
ItemsSource="{x:Bind GetMappedNewRemapKeys(50)}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" Spacing="4" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
<Border
|
||||
Margin="16,0,0,0"
|
||||
Padding="12,4,12,6"
|
||||
VerticalAlignment="Center"
|
||||
CornerRadius="12">
|
||||
<Border.Background>
|
||||
<SolidColorBrush Opacity="0.3" Color="{ThemeResource SystemAccentColor}" />
|
||||
</Border.Background>
|
||||
<TextBlock Text="{x:Bind TargetApp}" />
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsCard.Description>
|
||||
</tkcontrols:SettingsCard>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
|
@ -2367,6 +2367,10 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<value>to</value>
|
||||
<comment>as in: from x to y</comment>
|
||||
</data>
|
||||
<data name="Starts.Text" xml:space="preserve">
|
||||
<value>starts</value>
|
||||
<comment>as in: doing x will start y</comment>
|
||||
</data>
|
||||
<data name="LearnMore_Awake.Text" xml:space="preserve">
|
||||
<value>Learn more about Awake</value>
|
||||
<comment>Awake is a product name, do not loc</comment>
|
||||
|
@ -190,11 +190,11 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
else if (appSpecificShortcutList == null)
|
||||
{
|
||||
return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, NewRemapString = x.NewRemapString, TargetApp = allAppsDescription }).ToList();
|
||||
return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, NewRemapString = x.NewRemapString, RunProgramFilePath = x.RunProgramFilePath, OperationType = x.OperationType, OpenUri = x.OpenUri, SecondKeyOfChord = x.SecondKeyOfChord, RunProgramArgs = x.RunProgramArgs, TargetApp = allAppsDescription }).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, NewRemapString = x.NewRemapString, TargetApp = allAppsDescription }).Concat(appSpecificShortcutList).ToList();
|
||||
return globalShortcutList.ConvertAll(x => new AppSpecificKeysDataModel { OriginalKeys = x.OriginalKeys, NewRemapKeys = x.NewRemapKeys, NewRemapString = x.NewRemapString, RunProgramFilePath = x.RunProgramFilePath, OperationType = x.OperationType, OpenUri = x.OpenUri, SecondKeyOfChord = x.SecondKeyOfChord, RunProgramArgs = x.RunProgramArgs, TargetApp = allAppsDescription }).Concat(appSpecificShortcutList).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,18 @@ map<wstring, vector<wstring>> escapeInfo = {
|
||||
{ L"FancyZones\\app-zone-history.json", { L"app-zone-history/app-path" } },
|
||||
{ L"FancyZones\\settings.json", { L"properties/fancyzones_excluded_apps" } },
|
||||
{ L"MouseWithoutBorders\\settings.json", { L"properties/SecurityKey" } }, // avoid leaking connection key
|
||||
{ L"Keyboard Manager\\default.json", { L"remapKeysToText", L"remapShortcutsToText" } }, // avoid leaking personal information from text mappings
|
||||
{ L"Keyboard Manager\\default.json", {
|
||||
L"remapKeysToText",
|
||||
L"remapShortcutsToText",
|
||||
L"remapShortcuts/global/runProgramFilePath",
|
||||
L"remapShortcuts/global/runProgramArgs",
|
||||
L"remapShortcuts/global/runProgramStartInDir",
|
||||
L"remapShortcuts/global/openUri",
|
||||
L"remapShortcuts/appSpecific/runProgramFilePath",
|
||||
L"remapShortcuts/appSpecific/runProgramArgs",
|
||||
L"remapShortcuts/appSpecific/runProgramStartInDir",
|
||||
L"remapShortcuts/appSpecific/openUri",
|
||||
} }, // avoid leaking personal information from text, URI or application mappings
|
||||
};
|
||||
|
||||
vector<wstring> filesToDelete = {
|
||||
|
Loading…
Reference in New Issue
Block a user