mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-29 16:36:40 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79eda1681b | ||
|
|
1360359bba | ||
|
|
1a10c1b4f9 | ||
|
|
ae08b810bb | ||
|
|
2baaa1f20e | ||
|
|
25f0ba19ca | ||
|
|
32d873f41d | ||
|
|
772387a27a |
42
README.md
42
README.md
@@ -72,9 +72,9 @@ PowerToys will now enable two types of files to be previewed: Markdown (.md) & S
|
||||
- Windows 10 1803 (build 17134) or later.
|
||||
- Have [.NET Core 3.1 Desktop Runtime](https://dotnet.microsoft.com/download/dotnet-core/thank-you/runtime-desktop-3.1.4-windows-x64-installer). The installer will prompt this but we want to directly make people aware.
|
||||
|
||||
### Via Github with MSI [Recommended]
|
||||
### Via GitHub with MSI [Recommended]
|
||||
|
||||
Install from the [Microsoft PowerToys GitHub releases page][github-release-link]. Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.18.2-x64.msi` to download the PowerToys installer.
|
||||
Install from the [Microsoft PowerToys GitHub releases page][github-release-link]. Click on `Assets` to show the files available in the release and then click on `PowerToysSetup-0.19.0-x64.msi` to download the PowerToys installer.
|
||||
|
||||
**Note:** After installing, you will have to start PowerToys for the first time. We will improve install experience this moving forward but due to a possible install dependency, we can't start after install currently.
|
||||
|
||||
@@ -107,8 +107,9 @@ choco upgrade powertoys
|
||||
|
||||
### Known issues
|
||||
|
||||
- [#2012 - Uninstalling with old control panel fails](https://github.com/microsoft/PowerToys/issues/2012): Please use the modern settings to uninstall. `Windows 10 Settings -> Apps -> Apps & features`
|
||||
- [#3384 - PowerToys Settings window is empty](https://github.com/microsoft/PowerToys/issues/3384): Workaround appears to be run as admin. We are proactively looking into this as a hotfix.
|
||||
- PT Run, Newly installed apps can't be found [#3553](https://github.com/microsoft/PowerToys/issues/3553). We will address this in 0.20.
|
||||
- PT Run, CPU / Memory, still investigating [#3208](https://github.com/microsoft/PowerToys/issues/3208). We have 2 leads and fixed one item.
|
||||
- WinKey remapping for PT Run can be quirky [#4578](https://github.com/microsoft/PowerToys/issues/4578)
|
||||
|
||||
### Processor support
|
||||
|
||||
@@ -120,26 +121,29 @@ We currently support the matrix below.
|
||||
|
||||
## What's Happening
|
||||
|
||||
### May 2020 Update
|
||||
### June 2020 Update
|
||||
|
||||
Our goals for 0.18 release cycle was three big items, PowerToys Run, Keyboard manager, and migrating to the new settings system. This is also the first time we'll test out the auto-updating system.
|
||||
Our goals for 0.19 release cycle had one big goal, add in stability / quality fixes. We've addressed over 100 issues across all our utilities. We've improved our installer experience and parts will start coming online in 0.19 and 0.20. In this release, it will be the last time during upgrade you'll see Windows Explorer flash on you. For 0.20, the .NET Core install experience much smoother.
|
||||
|
||||
Feedback is critical. We know there are areas for improvement on PT Run. We would love feedback so we can improve. We also would love to know if you want us to be more aggressive on auto-upgrading.
|
||||
We'd also stress feedback is critical. We know there are areas for improvement on PowerToys Run. We would love feedback so we can improve. We also would love to know if you want us to be more aggressive on auto-upgrading.
|
||||
|
||||
- We shipped [v0.18][github-release-link]!
|
||||
- New Utilities
|
||||
- PowerToys Run, our new application launcher (use alt-space to activate)
|
||||
- Keyboard manager, a quick easy way to remap your keyboard
|
||||
- Fixed [#243](https://github.com/microsoft/PowerToys/issues/243)'s setting issue
|
||||
- Improved performance on FancyZones [#1264](https://github.com/microsoft/PowerToys/issues/1264)
|
||||
- Lots of bug fixes!
|
||||
Lastly, we'd like to thank everyone who filed a bug, gave feedback or made a pull-request. The PowerToys team is extremely grateful to have the support of an amazing active community.
|
||||
|
||||
For [0.19](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F4), we are proactively working on:
|
||||
- We shipped [v0.19][github-release-link]!
|
||||
- Big push for PowerToys Run search quality fixes
|
||||
- PowerToys Run can now remap to any key shortcut (minus restricted ones such as WinKey+L)
|
||||
- Improved FancyZones on Virtual Desktops and multi-thread design
|
||||
- Installer after 0.19 will no longer restart Windows Explorer
|
||||
- Fixed [#2012 - Uninstalling with old control panel fails](https://github.com/microsoft/PowerToys/issues/2012)
|
||||
- Fixed [#3384 - PowerToys Settings window is empty](https://github.com/microsoft/PowerToys/issues/3384)
|
||||
- Over 100 bug fixes!
|
||||
|
||||
- Enable PT Run to be mapped to Win-Keys
|
||||
- Stability / tech debt fixes
|
||||
- Performance improvements with FancyZones
|
||||
- A testing utility for FancyZones to be sure we can test different window configurations.
|
||||
For [0.20](https://github.com/microsoft/PowerToys/issues?q=is%3Aopen+is%3Aissue+project%3Amicrosoft%2FPowerToys%2F6), we are proactively working on:
|
||||
|
||||
- Stability
|
||||
- Start work on FZ Editor V2
|
||||
- Start work on OOBE improvements
|
||||
- Keyboard manager improvements
|
||||
|
||||
### Version 1.0 plan
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Version>0.19.0</Version>
|
||||
<Version>0.19.1</Version>
|
||||
<DefineConstants>Version=$(Version);</DefineConstants>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -5,15 +5,14 @@ using namespace interop;
|
||||
|
||||
HotkeyManager::HotkeyManager()
|
||||
{
|
||||
keyboardEventCallback = gcnew KeyboardEventCallback(this, &HotkeyManager::KeyboardEventProc);
|
||||
keyboardEventCallback = gcnew KeyboardEventCallback(this, &HotkeyManager::KeyboardEventProc);
|
||||
isActiveCallback = gcnew IsActiveCallback(this, &HotkeyManager::IsActiveProc);
|
||||
filterKeyboardCallback = gcnew FilterKeyboardEvent(this, &HotkeyManager::FilterKeyboardProc);
|
||||
|
||||
keyboardHook = gcnew KeyboardHook(
|
||||
keyboardEventCallback,
|
||||
isActiveCallback,
|
||||
filterKeyboardCallback
|
||||
);
|
||||
filterKeyboardCallback);
|
||||
hotkeys = gcnew Dictionary<HOTKEY_HANDLE, HotkeyCallback ^>();
|
||||
pressedKeys = gcnew Hotkey();
|
||||
keyboardHook->Start();
|
||||
@@ -25,8 +24,9 @@ HotkeyManager::~HotkeyManager()
|
||||
}
|
||||
|
||||
// When all Shortcut keys are pressed, fire the HotkeyCallback event.
|
||||
void HotkeyManager::KeyboardEventProc(KeyboardEvent^ ev)
|
||||
void HotkeyManager::KeyboardEventProc(KeyboardEvent ^ ev)
|
||||
{
|
||||
// pressedKeys always stores the latest keyboard state
|
||||
auto pressedKeysHandle = GetHotkeyHandle(pressedKeys);
|
||||
if (hotkeys->ContainsKey(pressedKeysHandle))
|
||||
{
|
||||
@@ -42,22 +42,24 @@ bool HotkeyManager::IsActiveProc()
|
||||
}
|
||||
|
||||
// KeyboardEvent callback is only fired for relevant key events.
|
||||
bool HotkeyManager::FilterKeyboardProc(KeyboardEvent^ ev)
|
||||
bool HotkeyManager::FilterKeyboardProc(KeyboardEvent ^ ev)
|
||||
{
|
||||
auto oldHandle = GetHotkeyHandle(pressedKeys);
|
||||
|
||||
// Updating the pressed keys here so we know if the keypress event
|
||||
// should be propagated or not.
|
||||
UpdatePressedKeys(ev);
|
||||
// Updating the pressed keys here so we know if the keypress event should be propagated or not.
|
||||
pressedKeys->Win = (GetAsyncKeyState(VK_LWIN) & 0x8000) || (GetAsyncKeyState(VK_RWIN) & 0x8000);
|
||||
pressedKeys->Ctrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
|
||||
pressedKeys->Alt = GetAsyncKeyState(VK_MENU) & 0x8000;
|
||||
pressedKeys->Shift = GetAsyncKeyState(VK_SHIFT) & 0x8000;
|
||||
pressedKeys->Key = ev->key;
|
||||
|
||||
// Convert to hotkey handle
|
||||
auto pressedKeysHandle = GetHotkeyHandle(pressedKeys);
|
||||
|
||||
// Check if the hotkey matches the pressed keys, and check if the pressed keys aren't duplicate
|
||||
// (there shouldn't be auto repeating hotkeys)
|
||||
if (hotkeys->ContainsKey(pressedKeysHandle) && oldHandle != pressedKeysHandle)
|
||||
// Check if any hotkey matches the pressed keys if the current key event is a key down event
|
||||
if ((ev->message == WM_KEYDOWN || ev->message == WM_SYSKEYDOWN) && hotkeys->ContainsKey(pressedKeysHandle))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -83,51 +85,3 @@ HOTKEY_HANDLE HotkeyManager::GetHotkeyHandle(Hotkey ^ hotkey)
|
||||
handle |= hotkey->Alt << 11;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void HotkeyManager::UpdatePressedKey(DWORD code, bool replaceWith, unsigned char replaceWithKey)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case VK_LWIN:
|
||||
case VK_RWIN:
|
||||
pressedKeys->Win = replaceWith;
|
||||
break;
|
||||
case VK_CONTROL:
|
||||
case VK_LCONTROL:
|
||||
case VK_RCONTROL:
|
||||
pressedKeys->Ctrl = replaceWith;
|
||||
break;
|
||||
case VK_SHIFT:
|
||||
case VK_LSHIFT:
|
||||
case VK_RSHIFT:
|
||||
pressedKeys->Shift = replaceWith;
|
||||
break;
|
||||
case VK_MENU:
|
||||
case VK_LMENU:
|
||||
case VK_RMENU:
|
||||
pressedKeys->Alt = replaceWith;
|
||||
break;
|
||||
default:
|
||||
pressedKeys->Key = replaceWithKey;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HotkeyManager::UpdatePressedKeys(KeyboardEvent ^ ev)
|
||||
{
|
||||
switch (ev->message)
|
||||
{
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
{
|
||||
UpdatePressedKey(ev->key, true, ev->key);
|
||||
}
|
||||
break;
|
||||
case WM_KEYUP:
|
||||
case WM_SYSKEYUP:
|
||||
{
|
||||
UpdatePressedKey(ev->key, false, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public
|
||||
public
|
||||
delegate void HotkeyCallback();
|
||||
|
||||
typedef unsigned short HOTKEY_HANDLE;
|
||||
typedef unsigned short HOTKEY_HANDLE;
|
||||
|
||||
public
|
||||
ref class HotkeyManager
|
||||
@@ -46,12 +46,9 @@ public
|
||||
IsActiveCallback ^ isActiveCallback;
|
||||
FilterKeyboardEvent ^ filterKeyboardCallback;
|
||||
|
||||
|
||||
void KeyboardEventProc(KeyboardEvent ^ ev);
|
||||
bool IsActiveProc();
|
||||
bool FilterKeyboardProc(KeyboardEvent ^ ev);
|
||||
HOTKEY_HANDLE GetHotkeyHandle(Hotkey ^ hotkey);
|
||||
void UpdatePressedKeys(KeyboardEvent ^ ev);
|
||||
void UpdatePressedKey(DWORD code, bool replaceWith, unsigned char replaceWithKey);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@ KeyboardHook::KeyboardHook(
|
||||
IsActiveCallback ^ isActiveCallback,
|
||||
FilterKeyboardEvent ^ filterKeyboardEvent)
|
||||
{
|
||||
kbEventDispatch = gcnew Thread(gcnew ThreadStart(this, &KeyboardHook::DispatchProc));
|
||||
queue = gcnew Queue<KeyboardEvent ^>();
|
||||
this->keyboardEventCallback = keyboardEventCallback;
|
||||
this->isActiveCallback = isActiveCallback;
|
||||
this->filterKeyboardEvent = filterKeyboardEvent;
|
||||
@@ -24,44 +22,10 @@ KeyboardHook::KeyboardHook(
|
||||
|
||||
KeyboardHook::~KeyboardHook()
|
||||
{
|
||||
quit = true;
|
||||
|
||||
// Notify the DispatchProc thread so that it isn't stuck at the Wait step
|
||||
Monitor::Enter(queue);
|
||||
Monitor::Pulse(queue);
|
||||
Monitor::Exit(queue);
|
||||
|
||||
kbEventDispatch->Join();
|
||||
|
||||
// Unregister low level hook procedure
|
||||
UnhookWindowsHookEx(hookHandle);
|
||||
}
|
||||
|
||||
void KeyboardHook::DispatchProc()
|
||||
{
|
||||
Monitor::Enter(queue);
|
||||
quit = false;
|
||||
while (!quit)
|
||||
{
|
||||
if (queue->Count == 0)
|
||||
{
|
||||
Monitor::Wait(queue);
|
||||
continue;
|
||||
}
|
||||
auto nextEv = queue->Dequeue();
|
||||
|
||||
// Release lock while callback is being invoked
|
||||
Monitor::Exit(queue);
|
||||
|
||||
keyboardEventCallback->Invoke(nextEv);
|
||||
|
||||
// Re-aquire lock
|
||||
Monitor::Enter(queue);
|
||||
}
|
||||
|
||||
Monitor::Exit(queue);
|
||||
}
|
||||
|
||||
void KeyboardHook::Start()
|
||||
{
|
||||
hookProc = gcnew HookProcDelegate(this, &KeyboardHook::HookProc);
|
||||
@@ -85,8 +49,6 @@ void KeyboardHook::Start()
|
||||
throw std::exception("SetWindowsHookEx failed.");
|
||||
}
|
||||
}
|
||||
|
||||
kbEventDispatch->Start();
|
||||
}
|
||||
|
||||
LRESULT CALLBACK KeyboardHook::HookProc(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
@@ -101,10 +63,7 @@ LRESULT CALLBACK KeyboardHook::HookProc(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
Monitor::Enter(queue);
|
||||
queue->Enqueue(ev);
|
||||
Monitor::Pulse(queue);
|
||||
Monitor::Exit(queue);
|
||||
keyboardEventCallback->Invoke(ev);
|
||||
return 1;
|
||||
}
|
||||
return CallNextHookEx(hookHandle, nCode, wParam, lParam);
|
||||
|
||||
@@ -33,16 +33,12 @@ public
|
||||
|
||||
private:
|
||||
delegate LRESULT HookProcDelegate(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
Thread ^ kbEventDispatch;
|
||||
Queue<KeyboardEvent ^> ^ queue;
|
||||
KeyboardEventCallback ^ keyboardEventCallback;
|
||||
IsActiveCallback ^ isActiveCallback;
|
||||
FilterKeyboardEvent ^ filterKeyboardEvent;
|
||||
bool quit;
|
||||
HHOOK hookHandle;
|
||||
HookProcDelegate ^ hookProc;
|
||||
|
||||
void DispatchProc();
|
||||
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam);
|
||||
};
|
||||
|
||||
|
||||
@@ -9,13 +9,7 @@
|
||||
template<uint16_t APIVersion>
|
||||
bool IsAPIContractVxAvailable()
|
||||
{
|
||||
static bool isAPIContractVxAvailableInitialized = false;
|
||||
static bool isAPIContractVxAvailable = false;
|
||||
if (!isAPIContractVxAvailableInitialized)
|
||||
{
|
||||
isAPIContractVxAvailableInitialized = true;
|
||||
isAPIContractVxAvailable = winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", APIVersion);
|
||||
}
|
||||
static bool isAPIContractVxAvailable = winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", APIVersion);
|
||||
|
||||
return isAPIContractVxAvailable;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Microsoft.PowerLauncher.Telemetry
|
||||
/// <summary>
|
||||
/// Gets The version string. TODO: This should be replaced by a P/Invoke call to get_product_version
|
||||
/// </summary>
|
||||
public string Version => "v0.19.0";
|
||||
public string Version => "v0.19.1";
|
||||
|
||||
public double BootTimeMs { get; set; }
|
||||
|
||||
|
||||
@@ -74,9 +74,6 @@ namespace Microsoft.PowerToys.Settings.UI.Runner
|
||||
MessageBoxButton.OK);
|
||||
app.Shutdown();
|
||||
}
|
||||
|
||||
// Terminate all threads of the process
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Identity
|
||||
Name="f4f787a5-f0ae-47a9-be89-5408b1dd2b47"
|
||||
Publisher="CN=lamotile"
|
||||
Version="0.19.0.0" />
|
||||
Version="0.19.1.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="f4f787a5-f0ae-47a9-be89-5408b1dd2b47" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -173,10 +173,7 @@ void WindowMoveHandlerPrivate::MoveSizeStart(HWND window, HMONITOR monitor, POIN
|
||||
m_mouseHook->enable();
|
||||
}
|
||||
|
||||
if (m_settings->GetSettings()->shiftDrag)
|
||||
{
|
||||
m_shiftHook->enable();
|
||||
}
|
||||
m_shiftHook->enable();
|
||||
|
||||
// This updates m_dragEnabled depending on if the shift key is being held down.
|
||||
UpdateDragState(window);
|
||||
|
||||
@@ -459,15 +459,15 @@ namespace KeyboardEventHandlers
|
||||
i++;
|
||||
}
|
||||
|
||||
// key down for original shortcut action key
|
||||
// key down for original shortcut action key with shortcut flag so that we don't invoke the same shortcut remap again
|
||||
if (isActionKeyPressed)
|
||||
{
|
||||
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)it.first.GetActionKey(), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Send current key pressed
|
||||
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
// Send current key pressed without shortcut flag so that it can be reprocessed in case the physical keys pressed are a different remapped shortcut
|
||||
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, 0);
|
||||
i++;
|
||||
|
||||
// Send dummy key since the current key pressed could be a modifier
|
||||
@@ -541,15 +541,15 @@ namespace KeyboardEventHandlers
|
||||
i++;
|
||||
}
|
||||
|
||||
// key down for original shortcut action key
|
||||
// key down for original shortcut action key with shortcut flag so that we don't invoke the same shortcut remap again
|
||||
if (isActionKeyPressed)
|
||||
{
|
||||
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)it.first.GetActionKey(), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Send current key pressed
|
||||
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
|
||||
// Send current key pressed without shortcut flag so that it can be reprocessed in case the physical keys pressed are a different remapped shortcut
|
||||
KeyboardManagerHelper::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, 0);
|
||||
i++;
|
||||
|
||||
// Send dummy key
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Microsoft.PowerLauncher.Telemetry
|
||||
/// <summary>
|
||||
/// TODO: This should be replaced by a P/Invoke call to get_product_version
|
||||
/// </summary>
|
||||
public string Version => "v0.19.0";
|
||||
public string Version => "v0.19.1";
|
||||
|
||||
public double BootTimeMs { get; set; }
|
||||
|
||||
|
||||
@@ -110,22 +110,31 @@ namespace PowerLauncher
|
||||
|
||||
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (Visibility == System.Windows.Visibility.Visible)
|
||||
if(e.PropertyName == nameof(MainViewModel.MainWindowVisibility))
|
||||
{
|
||||
// Not called on first launch
|
||||
// Additionally called when deactivated by clicking on screen
|
||||
UpdatePosition();
|
||||
BringProcessToForeground();
|
||||
|
||||
if (!_viewModel.LastQuerySelected)
|
||||
if (Visibility == System.Windows.Visibility.Visible)
|
||||
{
|
||||
_viewModel.LastQuerySelected = true;
|
||||
// Not called on first launch
|
||||
// Additionally called when deactivated by clicking on screen
|
||||
UpdatePosition();
|
||||
BringProcessToForeground();
|
||||
|
||||
if (!_viewModel.LastQuerySelected)
|
||||
{
|
||||
_viewModel.LastQuerySelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (e.PropertyName == nameof(MainViewModel.SystemQueryText))
|
||||
{
|
||||
this._isTextSetProgrammatically = true;
|
||||
SearchBox.QueryTextBox.Text = _viewModel.SystemQueryText;
|
||||
if (_viewModel.Results != null)
|
||||
{
|
||||
SearchBox.QueryTextBox.Text = MainViewModel.GetSearchText(
|
||||
_viewModel.Results.SelectedIndex,
|
||||
_viewModel.SystemQueryText,
|
||||
_viewModel.QueryText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,32 +270,20 @@ namespace PowerLauncher
|
||||
}
|
||||
|
||||
// To populate the AutoCompleteTextBox as soon as the selection is changed or set.
|
||||
// Setting it here instead of when the text is changed as there is a delay in executing the query and populating the result
|
||||
SearchBox.AutoCompleteTextBlock.Text = ListView_FirstItem(_viewModel.QueryText);
|
||||
// Setting it here instead of when the text is changed as there is a delay in executing the query and populating the result
|
||||
if (_viewModel.Results != null)
|
||||
{
|
||||
SearchBox.AutoCompleteTextBlock.Text = MainViewModel.GetAutoCompleteText(
|
||||
_viewModel.Results.SelectedIndex,
|
||||
_viewModel.Results.SelectedItem?.ToString(),
|
||||
_viewModel.QueryText);
|
||||
}
|
||||
}
|
||||
|
||||
private const int millisecondsToWait = 100;
|
||||
private static DateTime s_lastTimeOfTyping;
|
||||
private bool disposedValue = false;
|
||||
|
||||
private string ListView_FirstItem(String input)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
string selectedItem = _viewModel.Results?.SelectedItem?.ToString();
|
||||
int selectedIndex = _viewModel.Results.SelectedIndex;
|
||||
if (selectedItem != null && selectedIndex == 0)
|
||||
{
|
||||
if (selectedItem.IndexOf(input, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
{
|
||||
// Use the same case as the input query for the matched portion of the string
|
||||
return input + selectedItem.Substring(input.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_isTextSetProgrammatically)
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Wox.Plugin
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
[NotifyPropertyChangedInvocator]
|
||||
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
211
src/modules/launcher/Wox.Test/MainViewModelTest.cs
Normal file
211
src/modules/launcher/Wox.Test/MainViewModelTest.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Wox.Plugin;
|
||||
using Wox.ViewModel;
|
||||
|
||||
namespace Wox.Test
|
||||
{
|
||||
[TestFixture]
|
||||
class MainViewModelTest
|
||||
{
|
||||
[Test]
|
||||
public void MainViewModel_GetAutoCompleteTextReturnsEmptyString_WhenInputIsNull()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = null;
|
||||
String query = "M";
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, string.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetAutoCompleteTextReturnsEmptyString_WhenInputIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = string.Empty;
|
||||
String query = "M";
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, string.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetAutoCompleteTextReturnsEmptyString_WhenQueryIsNull()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = "M";
|
||||
String query = null;
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, string.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetAutoCompleteTextReturnsEmptyString_WhenQueryIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = "M";
|
||||
String query = string.Empty;
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, string.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetAutoCompleteTextReturnsEmptyString_WhenIndexIsNonZero()
|
||||
{
|
||||
// Arrange
|
||||
int index = 2;
|
||||
string input = "Visual";
|
||||
String query = "Vis";
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, string.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetAutoCompleteTextReturnsMatchingString_WhenIndexIsZeroAndMatch()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = "VISUAL";
|
||||
String query = "VIs";
|
||||
string ExpectedAutoCompleteText = "VIsUAL";
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, ExpectedAutoCompleteText);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetAutoCompleteTextReturnsEmptyString_WhenIndexIsZeroAndNoMatch()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = "VISUAL";
|
||||
String query = "Vim";
|
||||
string ExpectedAutoCompleteText = string.Empty;
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetAutoCompleteText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, ExpectedAutoCompleteText);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetSearchTextReturnsEmptyString_WhenInputIsNull()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = null;
|
||||
String query = "M";
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetSearchText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, string.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetSearchTextReturnsEmptyString_WhenInputIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = string.Empty;
|
||||
String query = "M";
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetSearchText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, string.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetSearchTextReturnsInputString_WhenQueryIsNull()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = "Visual";
|
||||
String query = null;
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetSearchText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, input);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetSearchTextReturnsInputString_WhenQueryIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = "Visual";
|
||||
String query = string.Empty;
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetSearchText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, input);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetSearchTextReturnsMatchingStringWithCase_WhenIndexIsZeroAndMatch()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = "VISUAL";
|
||||
String query = "VIs";
|
||||
string ExpectedAutoCompleteText = "VIsUAL";
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetSearchText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, ExpectedAutoCompleteText);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MainViewModel_GetSearchTextReturnsInput_WhenIndexIsZeroAndNoMatch()
|
||||
{
|
||||
// Arrange
|
||||
int index = 0;
|
||||
string input = "VISUAL";
|
||||
String query = "Vim";
|
||||
|
||||
// Act
|
||||
string autoCompleteText = MainViewModel.GetSearchText(index, input, query);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(autoCompleteText, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@
|
||||
<ProjectReference Include="..\Wox.Core\Wox.Core.csproj" />
|
||||
<ProjectReference Include="..\Wox.Infrastructure\Wox.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\Wox.Plugin\Wox.Plugin.csproj" />
|
||||
<ProjectReference Include="..\Wox\Wox.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
58
src/modules/launcher/Wox.Test/WoxTest.cs
Normal file
58
src/modules/launcher/Wox.Test/WoxTest.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Windows.Input;
|
||||
using NUnit.Framework;
|
||||
using Wox.Plugin;
|
||||
using Wox.ViewModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Wox.Test
|
||||
{
|
||||
[TestFixture]
|
||||
public class Wox
|
||||
{
|
||||
// A Dummy class to test that OnPropertyChanged() is called while we set the variable
|
||||
public class DummyTestClass : BaseModel
|
||||
{
|
||||
public bool isFunctionCalled = false;
|
||||
private ICommand _item;
|
||||
|
||||
public ICommand Item
|
||||
{
|
||||
get
|
||||
{
|
||||
return this._item;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value != this._item)
|
||||
{
|
||||
this._item = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overriding the OnPropertyChanged() function to test if it is being called
|
||||
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
isFunctionCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void AnyVariable_MustCallOnPropertyChanged_WhenSet()
|
||||
{
|
||||
// Arrange
|
||||
DummyTestClass testClass = new DummyTestClass();
|
||||
|
||||
// Act
|
||||
testClass.Item = new RelayCommand(null);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(testClass.isFunctionCalled);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,29 @@ namespace Wox.ViewModel
|
||||
{
|
||||
public class ContextMenuItemViewModel : BaseModel
|
||||
{
|
||||
private ICommand _command;
|
||||
|
||||
public string PluginName { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Glyph { get; set; }
|
||||
public string FontFamily { get; set; }
|
||||
public ICommand Command { get; set; }
|
||||
public ICommand Command {
|
||||
get
|
||||
{
|
||||
return this._command;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
// ICommand does not implement the INotifyPropertyChanged interface and must call OnPropertyChanged() to prevent memory leaks
|
||||
if (value != this._command)
|
||||
{
|
||||
this._command = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Key AcceleratorKey { get; set; }
|
||||
public ModifierKeys AcceleratorModifiers { get; set; }
|
||||
public bool IsAcceleratorKeyEnabled { get; set; }
|
||||
|
||||
@@ -709,6 +709,40 @@ namespace Wox.ViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetAutoCompleteText(int index, string input, String query)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input) && !string.IsNullOrEmpty(query))
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
if (input.IndexOf(query, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
{
|
||||
// Use the same case as the input query for the matched portion of the string
|
||||
return query + input.Substring(query.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string GetSearchText(int index, String input, string query)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
if (index == 0 && !string.IsNullOrEmpty(query))
|
||||
{
|
||||
if (input.IndexOf(query, StringComparison.InvariantCultureIgnoreCase) == 0)
|
||||
{
|
||||
return query + input.Substring(query.Length);
|
||||
}
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Wox.ViewModel
|
||||
Hover
|
||||
};
|
||||
|
||||
public List<ContextMenuItemViewModel> ContextMenuItems { get; set; }
|
||||
public ObservableCollection<ContextMenuItemViewModel> ContextMenuItems { get; set; } = new ObservableCollection<ContextMenuItemViewModel>();
|
||||
|
||||
public ICommand ActivateContextButtonsHoverCommand { get; set; }
|
||||
public ICommand ActivateContextButtonsSelectionCommand { get; set; }
|
||||
@@ -46,7 +46,10 @@ namespace Wox.ViewModel
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
|
||||
ContextMenuSelectedIndex = NoSelectionIndex;
|
||||
LoadContextMenu();
|
||||
|
||||
ActivateContextButtonsHoverCommand = new RelayCommand(ActivateContextButtonsHoverAction);
|
||||
ActivateContextButtonsSelectionCommand = new RelayCommand(ActivateContextButtonsSelectionAction);
|
||||
DeactivateContextButtonsHoverCommand = new RelayCommand(DeactivateContextButtonsHoverAction);
|
||||
@@ -64,11 +67,6 @@ namespace Wox.ViewModel
|
||||
}
|
||||
public void ActivateContextButtons(ActivationType activationType)
|
||||
{
|
||||
if (ContextMenuItems == null)
|
||||
{
|
||||
LoadContextMenu();
|
||||
}
|
||||
|
||||
// Result does not contain any context menu items - we don't need to show the context menu ListView at all.
|
||||
if (ContextMenuItems.Count > 0)
|
||||
{
|
||||
@@ -78,14 +76,13 @@ namespace Wox.ViewModel
|
||||
{
|
||||
AreContextButtonsActive = false;
|
||||
}
|
||||
|
||||
|
||||
if (activationType == ActivationType.Selection)
|
||||
{
|
||||
IsSelected = true;
|
||||
EnableContextMenuAcceleratorKeys();
|
||||
}
|
||||
else if(activationType == ActivationType.Hover)
|
||||
else if (activationType == ActivationType.Hover)
|
||||
{
|
||||
IsHovered = true;
|
||||
}
|
||||
@@ -122,17 +119,17 @@ namespace Wox.ViewModel
|
||||
else
|
||||
{
|
||||
AreContextButtonsActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void LoadContextMenu()
|
||||
{
|
||||
var results = PluginManager.GetContextMenusForPlugin(Result);
|
||||
var newItems = new List<ContextMenuItemViewModel>();
|
||||
ContextMenuItems.Clear();
|
||||
foreach (var r in results)
|
||||
{
|
||||
newItems.Add(new ContextMenuItemViewModel
|
||||
ContextMenuItems.Add(new ContextMenuItemViewModel()
|
||||
{
|
||||
PluginName = r.PluginName,
|
||||
Title = r.Title,
|
||||
@@ -155,13 +152,11 @@ namespace Wox.ViewModel
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
ContextMenuItems = newItems;
|
||||
}
|
||||
|
||||
private void EnableContextMenuAcceleratorKeys()
|
||||
{
|
||||
foreach(var i in ContextMenuItems)
|
||||
foreach (var i in ContextMenuItems)
|
||||
{
|
||||
i.IsAcceleratorKeyEnabled = true;
|
||||
}
|
||||
@@ -192,7 +187,7 @@ namespace Wox.ViewModel
|
||||
imagePath = ImageLoader.ErrorIconPath;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// will get here either when icoPath has value\icon delegate is null\when had exception in delegate
|
||||
return ImageLoader.Load(imagePath);
|
||||
}
|
||||
@@ -201,10 +196,10 @@ namespace Wox.ViewModel
|
||||
//Returns false if we've already reached the last item.
|
||||
public bool SelectNextContextButton()
|
||||
{
|
||||
if(ContextMenuSelectedIndex == (ContextMenuItems.Count -1))
|
||||
if (ContextMenuSelectedIndex == (ContextMenuItems.Count - 1))
|
||||
{
|
||||
ContextMenuSelectedIndex = NoSelectionIndex;
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
ContextMenuSelectedIndex++;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace MarkdownPreviewHandler.Telemetry.Events
|
||||
/// <summary>
|
||||
/// Gets The version string. TODO: This should be replaced by a P/Invoke call to get_product_version.
|
||||
/// </summary>
|
||||
public string Version => "v0.19.0";
|
||||
public string Version => "v0.19.1";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace SvgPreviewHandler.Telemetry.Events
|
||||
/// <summary>
|
||||
/// Gets The version string. TODO: This should be replaced by a P/Invoke call to get_product_version.
|
||||
/// </summary>
|
||||
public string Version => "v0.19.0";
|
||||
public string Version => "v0.19.1";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
|
||||
Reference in New Issue
Block a user