Skip to content

Commit

Permalink
Remap Keyboard UI (dev/keyboardManager) (#1698)
Browse files Browse the repository at this point in the history
* Added initial UI implementation

* Added backend logic for remap key interaction

* Added single key remap control

* Updated Edit keyboardWindow UI

* Commented out ui logic

* Fixed issue with remap window UI and uncommented the code

* Updated customdialog window foreground color

* Updated buttons foreground color

* Added info header

* Added null check for detected key

* Removed fully qualified namespaces

* updated the background color as ligtht gray
  • Loading branch information
udit3333 committed Apr 8, 2020
1 parent 90ddcb3 commit f48040a
Show file tree
Hide file tree
Showing 8 changed files with 392 additions and 14 deletions.
60 changes: 57 additions & 3 deletions src/modules/keyboardmanager/common/KeyboardManagerState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

// Constructor
KeyboardManagerState::KeyboardManagerState() :
uiState(KeyboardManagerUIState::Deactivated), currentUIWindow(nullptr), currentShortcutTextBlock(nullptr)
uiState(KeyboardManagerUIState::Deactivated), currentUIWindow(nullptr), currentShortcutTextBlock(nullptr), currentSingleKeyRemapTextBlock(nullptr)
{
}

Expand Down Expand Up @@ -46,6 +46,10 @@ void KeyboardManagerState::ResetUIState()
SetUIState(KeyboardManagerUIState::Deactivated);
currentShortcutTextBlock = nullptr;
detectedShortcut.clear();

// Reset all the single key remap stored variables.
currentSingleKeyRemapTextBlock = nullptr;
detectedRemapKey = NULL;
}

// Function to clear the OS Level shortcut remapping table
Expand All @@ -54,18 +58,36 @@ void KeyboardManagerState::ClearOSLevelShortcuts()
osLevelShortcutReMap.clear();
}

// Function to clear the Keys remapping table.
void KeyboardManagerState::ClearSingleKeyRemaps()
{
singleKeyReMap.clear();
}

// Function to add a new OS level shortcut remapping
void KeyboardManagerState::AddOSLevelShortcut(const std::vector<DWORD>& originalSC, const std::vector<WORD>& newSC)
{
osLevelShortcutReMap[originalSC] = std::make_pair(newSC, false);
}

// Function to add a new OS level shortcut remapping
void KeyboardManagerState::AddSingleKeyRemap(const DWORD& originalKey, const WORD& newRemapKey)
{
singleKeyReMap[originalKey] = newRemapKey;
}

// Function to set the textblock of the detect shortcut UI so that it can be accessed by the hook
void KeyboardManagerState::ConfigureDetectShortcutUI(const TextBlock& textBlock)
{
currentShortcutTextBlock = textBlock;
}

// Function to set the textblock of the detect remap key UI so that it can be accessed by the hook
void KeyboardManagerState::ConfigureDetectSingleKeyRemapUI(const TextBlock& textBlock)
{
currentSingleKeyRemapTextBlock = textBlock;
}

// Function to update the detect shortcut UI based on the entered keys
void KeyboardManagerState::UpdateDetectShortcutUI()
{
Expand All @@ -82,6 +104,22 @@ void KeyboardManagerState::UpdateDetectShortcutUI()
});
}

// Function to update the detect remap key UI based on the entered key.
void KeyboardManagerState::UpdateDetectSingleKeyRemapUI()
{
if (currentSingleKeyRemapTextBlock == nullptr)
{
return;
}

hstring remapKeyString = winrt::to_hstring((unsigned int)detectedRemapKey);

// Since this function is invoked from the back-end thread, in order to update the UI the dispatcher must be used.
currentSingleKeyRemapTextBlock.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, [=]() {
currentSingleKeyRemapTextBlock.Text(remapKeyString);
});
}

// Function to return the currently detected shortcut which is displayed on the UI
std::vector<DWORD> KeyboardManagerState::GetDetectedShortcut()
{
Expand All @@ -91,12 +129,28 @@ std::vector<DWORD> KeyboardManagerState::GetDetectedShortcut()
return convertWStringVectorToIntegerVector<DWORD>(detectedShortcutVector);
}

// Function to return the currently detected remap key which is displayed on the UI
DWORD KeyboardManagerState::GetDetectedSingleRemapKey()
{
hstring remapKeyString = currentSingleKeyRemapTextBlock.Text();
std::wstring remapKeyWString = remapKeyString.c_str();
DWORD remapKey = NULL;
if (!remapKeyString.empty())
{
remapKey = std::stoul(remapKeyWString);
}

return remapKey;
}

// 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.
bool KeyboardManagerState::DetectKeyUIBackend(LowlevelKeyboardEvent* data)
bool KeyboardManagerState::DetectSingleRemapKeyUIBackend(LowlevelKeyboardEvent* data)
{
// Check if the detect key UI window has been activated
if (CheckUIState(KeyboardManagerUIState::DetectKeyWindowActivated))
if (CheckUIState(KeyboardManagerUIState::DetectSingleKeyRemapWindowActivated))
{
detectedRemapKey = data->lParam->vkCode;
UpdateDetectSingleKeyRemapUI();
// Suppress the keyboard event
return true;
}
Expand Down
29 changes: 25 additions & 4 deletions src/modules/keyboardmanager/common/KeyboardManagerState.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ enum class KeyboardManagerUIState
// If set to this value then there is no keyboard manager window currently active that requires a hook
Deactivated,
// If set to this value then the detect key window is currently active and it requires a hook
DetectKeyWindowActivated,
DetectSingleKeyRemapWindowActivated,
// If set to this value then the detect shortcut window is currently active and it requires a hook
DetectShortcutWindowActivated
};
Expand All @@ -29,8 +29,14 @@ class KeyboardManagerState
// Vector to store the shortcut detected in the detect shortcut UI window. This is used in both the backend and the UI.
std::vector<DWORD> detectedShortcut;

// Store detected remap key in the remap UI window. This is used in both the backend and the UI.
DWORD detectedRemapKey;

// Stores the UI element which is to be updated based on the remap key entered.
TextBlock currentSingleKeyRemapTextBlock;

// Stores the UI element which is to be updated based on the shortcut entered
winrt::Windows::UI::Xaml::Controls::TextBlock currentShortcutTextBlock;
TextBlock currentShortcutTextBlock;

public:
// Maps which store the remappings for each of the features. The bool fields should be initalised to false. They are used to check the current state of the shortcut (i.e is that particular shortcut currently pressed down or not).
Expand Down Expand Up @@ -64,20 +70,35 @@ class KeyboardManagerState
// Function to clear the OS Level shortcut remapping table
void ClearOSLevelShortcuts();

// Function to clear the Keys remapping table
void ClearSingleKeyRemaps();

// Function to add a new single key remapping
void AddSingleKeyRemap(const DWORD& originalKey, const WORD& newRemapKey);

// Function to add a new OS level shortcut remapping
void AddOSLevelShortcut(const std::vector<DWORD>& originalSC, const std::vector<WORD>& newSC);

// Function to set the textblock of the detect shortcut UI so that it can be accessed by the hook
void ConfigureDetectShortcutUI(const winrt::Windows::UI::Xaml::Controls::TextBlock& textBlock);
void ConfigureDetectShortcutUI(const TextBlock& textBlock);

// Function to set the textblock of the detect remap key UI so that it can be accessed by the hook
void ConfigureDetectSingleKeyRemapUI(const TextBlock& textBlock);

// Function to update the detect shortcut UI based on the entered keys
void UpdateDetectShortcutUI();

// Function to update the detect remap key UI based on the entered key.
void UpdateDetectSingleKeyRemapUI();

// Function to return the currently detected shortcut which is displayed on the UI
std::vector<DWORD> GetDetectedShortcut();

// Function to return the currently detected remap key which is displayed on the UI
DWORD GetDetectedSingleRemapKey();

// 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.
bool DetectKeyUIBackend(LowlevelKeyboardEvent* data);
bool DetectSingleRemapKeyUIBackend(LowlevelKeyboardEvent* data);

// Function which can be used in HandleKeyboardHookEvent before the os level shortcut remap event to use the UI and suppress events while the remap window is active.
bool DetectShortcutUIBackend(LowlevelKeyboardEvent* data);
Expand Down
2 changes: 1 addition & 1 deletion src/modules/keyboardmanager/dll/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class PowerKeys : public PowertoyModuleIface
intptr_t HandleKeyboardHookEvent(LowlevelKeyboardEvent* data) noexcept
{
// If the Detect Key Window is currently activated, then suppress the keyboard event
if (keyboardManagerState.DetectKeyUIBackend(data))
if (keyboardManagerState.DetectSingleRemapKeyUIBackend(data))
{
return 1;
}
Expand Down
147 changes: 141 additions & 6 deletions src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "pch.h"
#include "EditKeyboardWindow.h"
#include "SingleKeyRemapControl.h"

LRESULT CALLBACK EditKeyboardWindowProc(HWND, UINT, WPARAM, LPARAM);

Expand Down Expand Up @@ -38,8 +39,8 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
400,
400,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInst,
Expand All @@ -63,14 +64,148 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
// Update the xaml island window size becuase initially is 0,0
SetWindowPos(hWndXamlIslandEditKeyboardWindow, 0, 0, 0, 400, 400, SWP_SHOWWINDOW);

//Creating the Xaml content
// Creating the Xaml content. xamlContainer is the parent UI element
Windows::UI::Xaml::Controls::StackPanel xamlContainer;
xamlContainer.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
Windows::UI::Xaml::Controls::Button bt;
bt.Content(winrt::box_value(winrt::to_hstring("Don't Type key")));
xamlContainer.Children().Append(bt);

// Header for the window
Windows::UI::Xaml::Controls::StackPanel header;
header.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
header.Margin({ 10, 10, 10, 30 });
header.Spacing(10);

// Header text
TextBlock headerText;
headerText.Text(winrt::to_hstring("Remap Keyboard"));
headerText.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Black() });
headerText.FontSize(30);
headerText.Margin({ 0, 0, 1000, 0 });

// Header Cancel button
Button cancelButton;
cancelButton.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
cancelButton.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Black() });
cancelButton.Content(winrt::box_value(winrt::to_hstring("Cancel")));
cancelButton.Click([&](IInspectable const& sender, RoutedEventArgs const&) {
// Close the window since settings do not need to be saved
PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0);
});

// Text block for information about remap key section.
TextBlock keyRemapInfoHeader;
keyRemapInfoHeader.Text(winrt::to_hstring("Select the key you want to remap, original key, and it's new output when pressed, the new key"));
keyRemapInfoHeader.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Black() });
keyRemapInfoHeader.Margin({ 0, 0, 0, 10 });

// Table to display the key remaps
Windows::UI::Xaml::Controls::StackPanel keyRemapTable;
keyRemapTable.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
keyRemapTable.Margin({ 10, 10, 10, 20 });
keyRemapTable.Spacing(10);

// Header row of the keys remap table
Windows::UI::Xaml::Controls::StackPanel tableHeaderRow;
tableHeaderRow.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
tableHeaderRow.Spacing(100);
tableHeaderRow.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);

// First header textblock in the header row of the keys remap table
TextBlock originalKeyRemapHeader;
originalKeyRemapHeader.Text(winrt::to_hstring("Original Key:"));
originalKeyRemapHeader.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Black() });
originalKeyRemapHeader.FontWeight(Text::FontWeights::Bold());
originalKeyRemapHeader.Margin({ 0, 0, 0, 10 });
tableHeaderRow.Children().Append(originalKeyRemapHeader);

// Second header textblock in the header row of the keys remap table
TextBlock newKeyRemapHeader;
newKeyRemapHeader.Text(winrt::to_hstring("New Key:"));
newKeyRemapHeader.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Black() });
newKeyRemapHeader.FontWeight(Text::FontWeights::Bold());
newKeyRemapHeader.Margin({ 0, 0, 0, 10 });
tableHeaderRow.Children().Append(newKeyRemapHeader);

keyRemapTable.Children().Append(tableHeaderRow);

// Message to display success/failure of saving settings.
TextBlock settingsMessage;

// Main Header Apply button
Button applyButton;
applyButton.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
applyButton.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Black() });
applyButton.Content(winrt::box_value(winrt::to_hstring("Apply")));
applyButton.Click([&](IInspectable const& sender, RoutedEventArgs const&) {
bool isSuccess = true;
// Clear existing Key Remaps
keyboardManagerState.ClearSingleKeyRemaps();

// Save the keys that are valid and report if any of them were invalid
for (unsigned int i = 1; i < keyRemapTable.Children().Size(); i++)
{
StackPanel currentRow = keyRemapTable.Children().GetAt(i).as<StackPanel>();
hstring originalKeyString = currentRow.Children().GetAt(0).as<StackPanel>().Children().GetAt(1).as<TextBlock>().Text();
hstring newKeyString = currentRow.Children().GetAt(1).as<StackPanel>().Children().GetAt(1).as<TextBlock>().Text();
if (!originalKeyString.empty() && !newKeyString.empty())
{
DWORD originalKey = std::stoi(originalKeyString.c_str());
DWORD newKey = std::stoi(newKeyString.c_str());
keyboardManagerState.AddSingleKeyRemap(originalKey, newKey);
}
else
{
isSuccess = false;
}
}

if (isSuccess)
{
settingsMessage.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Green() });
settingsMessage.Text(winrt::to_hstring("Remapping successful!"));
}
else
{
settingsMessage.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Red() });
settingsMessage.Text(winrt::to_hstring("All remappings were not successfully applied."));
}
});

header.Children().Append(headerText);
header.Children().Append(cancelButton);
header.Children().Append(applyButton);
header.Children().Append(settingsMessage);

// Store handle of edit keyboard window
SingleKeyRemapControl::EditKeyboardWindowHandle = _hWndEditKeyboardWindow;
// Store keyboard manager state
SingleKeyRemapControl::keyboardManagerState = &keyboardManagerState;

// Load existing remaps into UI
for (const auto& it : keyboardManagerState.singleKeyReMap)
{
SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, it.first, it.second);
}

// Add remap key button
Windows::UI::Xaml::Controls::Button addRemapKey;
addRemapKey.Background(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::LightGray() });
addRemapKey.Foreground(Windows::UI::Xaml::Media::SolidColorBrush{ Windows::UI::Colors::Black() });
FontIcon plusSymbol;
plusSymbol.FontFamily(Xaml::Media::FontFamily(L"Segoe MDL2 Assets"));
plusSymbol.Glyph(L"\xE109");
addRemapKey.Content(plusSymbol);
addRemapKey.Margin({ 10 });
addRemapKey.Click([&](IInspectable const& sender, RoutedEventArgs const&) {
SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable);
});

xamlContainer.Children().Append(header);
xamlContainer.Children().Append(keyRemapInfoHeader);
xamlContainer.Children().Append(keyRemapTable);
xamlContainer.Children().Append(addRemapKey);
xamlContainer.UpdateLayout();
desktopSource.Content(xamlContainer);

////End XAML Island section
if (_hWndEditKeyboardWindow)
{
Expand Down
2 changes: 2 additions & 0 deletions src/modules/keyboardmanager/ui/PowerKeysUI.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,15 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ShortcutControl.cpp" />
<ClCompile Include="SingleKeyRemapControl.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="EditKeyboardWindow.h" />
<ClInclude Include="EditShortcutsWindow.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="MainWindow.h" />
<ClInclude Include="ShortcutControl.h" />
<ClInclude Include="SingleKeyRemapControl.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down
2 changes: 2 additions & 0 deletions src/modules/keyboardmanager/ui/PowerKeysUI.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
<ClCompile Include="EditShortcutsWindow.cpp" />
<ClCompile Include="ShortcutControl.cpp" />
<ClCompile Include="pch.cpp" />
<ClCompile Include="SingleKeyRemapControl.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="MainWindow.h" />
<ClInclude Include="EditKeyboardWindow.h" />
<ClInclude Include="EditShortcutsWindow.h" />
<ClInclude Include="ShortcutControl.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="SingleKeyRemapControl.h" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down

0 comments on commit f48040a

Please sign in to comment.