diff --git a/src/modules/launcher/PowerLauncher/ActionKeywords.xaml.cs b/src/modules/launcher/PowerLauncher/ActionKeywords.xaml.cs index a3954876e7..fc94737d98 100644 --- a/src/modules/launcher/PowerLauncher/ActionKeywords.xaml.cs +++ b/src/modules/launcher/PowerLauncher/ActionKeywords.xaml.cs @@ -13,15 +13,13 @@ namespace Wox public partial class ActionKeywords : Window { private readonly Internationalization _translater = InternationalizationManager.Instance; + private readonly PluginPair _plugin; - private PluginPair _plugin; - private Settings _settings; - - public ActionKeywords(string pluginId, Settings settings) + public ActionKeywords(string pluginId) { InitializeComponent(); _plugin = PluginManager.GetPluginForId(pluginId); - _settings = settings; + if (_plugin == null) { MessageBox.Show(_translater.GetTranslation("cannotFindSpecifiedPlugin")); diff --git a/src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs b/src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs index e00fb7b0ce..2c2e756ea6 100644 --- a/src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs +++ b/src/modules/launcher/PowerLauncher/Helper/WindowsInteropHelper.cs @@ -16,7 +16,9 @@ namespace PowerLauncher.Helper { public static class WindowsInteropHelper { + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Matching COM")] private const int GWL_STYLE = -16; // WPF's Message code for Title Bar's Style + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Matching COM")] private const int WS_SYSMENU = 0x80000; // WPF's Message code for System Menu private static IntPtr _hwnd_shell; private static IntPtr _hwnd_desktop; @@ -42,8 +44,8 @@ namespace PowerLauncher.Helper [StructLayout(LayoutKind.Sequential)] internal struct INPUT { - public INPUTTYPE type; - public InputUnion data; + public INPUTTYPE Type; + public InputUnion Data; public static int Size { @@ -52,6 +54,7 @@ namespace PowerLauncher.Helper } [StructLayout(LayoutKind.Explicit)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")] internal struct InputUnion { [FieldOffset(0)] @@ -63,6 +66,7 @@ namespace PowerLauncher.Helper } [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")] internal struct MOUSEINPUT { internal int dx; @@ -74,6 +78,7 @@ namespace PowerLauncher.Helper } [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")] internal struct KEYBDINPUT { internal short wVk; @@ -84,6 +89,7 @@ namespace PowerLauncher.Helper } [StructLayout(LayoutKind.Sequential)] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")] internal struct HARDWAREINPUT { internal int uMsg; @@ -98,10 +104,10 @@ namespace PowerLauncher.Helper INPUTHARDWARE = 2, } - private const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass"; - private const string WINDOW_CLASS_WINTAB = "Flip3D"; - private const string WINDOW_CLASS_PROGMAN = "Progman"; - private const string WINDOW_CLASS_WORKERW = "WorkerW"; + private const string WindowClassConsole = "ConsoleWindowClass"; + private const string WindowClassWinTab = "Flip3D"; + private const string WindowClassProgman = "Progman"; + private const string WindowClassWorkerW = "WorkerW"; public static bool IsWindowFullscreen() { @@ -118,7 +124,7 @@ namespace PowerLauncher.Helper string windowClass = sb.ToString(); // for Win+Tab (Flip3D) - if (windowClass == WINDOW_CLASS_WINTAB) + if (windowClass == WindowClassWinTab) { return false; } @@ -126,13 +132,13 @@ namespace PowerLauncher.Helper _ = NativeMethods.GetWindowRect(hWnd, out RECT appBounds); // for console (ConsoleWindowClass), we have to check for negative dimensions - if (windowClass == WINDOW_CLASS_CONSOLE) + if (windowClass == WindowClassConsole) { return appBounds.Top < 0 && appBounds.Bottom < 0; } // for desktop (Progman or WorkerW, depends on the system), we have to check - if (windowClass == WINDOW_CLASS_PROGMAN || windowClass == WINDOW_CLASS_WORKERW) + if (windowClass == WindowClassProgman || windowClass == WindowClassWorkerW) { IntPtr hWndDesktop = NativeMethods.FindWindowEx(hWnd, IntPtr.Zero, "SHELLDLL_DefView", null); hWndDesktop = NativeMethods.FindWindowEx(hWndDesktop, IntPtr.Zero, "SysListView32", "FolderView"); diff --git a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs index 757fc35a71..efd45da458 100644 --- a/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs +++ b/src/modules/launcher/PowerLauncher/MainWindow.xaml.cs @@ -1,437 +1,437 @@ -// 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 System; -using System.ComponentModel; -using System.Timers; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using Microsoft.PowerLauncher.Telemetry; -using Microsoft.PowerToys.Telemetry; -using PowerLauncher.Helper; -using PowerLauncher.ViewModel; -using Wox.Infrastructure.UserSettings; -using KeyEventArgs = System.Windows.Input.KeyEventArgs; -using Screen = System.Windows.Forms.Screen; - -namespace PowerLauncher -{ - public partial class MainWindow : IDisposable - { - private Settings _settings; - private MainViewModel _viewModel; - private bool _isTextSetProgrammatically; - private bool _deletePressed = false; - private Timer _firstDeleteTimer = new Timer(); - private bool _coldStateHotkeyPressed = false; - - public MainWindow(Settings settings, MainViewModel mainVM) - : this() - { - DataContext = mainVM; - _viewModel = mainVM; - _settings = settings; - - InitializeComponent(); - - _firstDeleteTimer.Elapsed += CheckForFirstDelete; - _firstDeleteTimer.Interval = 1000; - } - - private void CheckForFirstDelete(object sender, ElapsedEventArgs e) - { - if (_firstDeleteTimer != null) - { - _firstDeleteTimer.Stop(); - if (_deletePressed) - { - PowerToysTelemetry.Log.WriteEvent(new LauncherFirstDeleteEvent()); - } - } - } - - public MainWindow() - { - InitializeComponent(); - } - - private void OnClosing(object sender, CancelEventArgs e) - { - _viewModel.Save(); - } - - private void BringProcessToForeground() - { - // Use SendInput hack to allow Activate to work - required to resolve focus issue https://github.com/microsoft/PowerToys/issues/4270 - WindowsInteropHelper.INPUT input = new WindowsInteropHelper.INPUT { type = WindowsInteropHelper.INPUTTYPE.INPUTMOUSE, data = { } }; - WindowsInteropHelper.INPUT[] inputs = new WindowsInteropHelper.INPUT[] { input }; - - // Send empty mouse event. This makes this thread the last to send input, and hence allows it to pass foreground permission checks - _ = NativeMethods.SendInput(1, inputs, WindowsInteropHelper.INPUT.Size); - Activate(); - } - - private void OnLoaded(object sender, RoutedEventArgs e) - { - WindowsInteropHelper.DisableControlBox(this); - InitializePosition(); - - SearchBox.QueryTextBox.DataContext = _viewModel; - SearchBox.QueryTextBox.PreviewKeyDown += Launcher_KeyDown; - SearchBox.QueryTextBox.TextChanged += QueryTextBox_TextChanged; - - // Set initial language flow direction - SearchBox_UpdateFlowDirection(); - - // Register language changed event - InputLanguageManager.Current.InputLanguageChanged += SearchBox_InputLanguageChanged; - - SearchBox.QueryTextBox.Focus(); - - ListBox.DataContext = _viewModel; - ListBox.SuggestionsList.SelectionChanged += SuggestionsList_SelectionChanged; - ListBox.SuggestionsList.PreviewMouseLeftButtonUp += SuggestionsList_PreviewMouseLeftButtonUp; - _viewModel.PropertyChanged += ViewModel_PropertyChanged; - - BringProcessToForeground(); - } - - private void SuggestionsList_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) - { - var result = ((FrameworkElement)e.OriginalSource).DataContext; - if (result != null) - { - // This may be null if the tapped item was one of the context buttons (run as admin etc). - if (result is ResultViewModel resultVM) - { - _viewModel.Results.SelectedItem = resultVM; - _viewModel.OpenResultCommand.Execute(null); - } - } - } - - private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(MainViewModel.MainWindowVisibility)) - { - if (Visibility == System.Windows.Visibility.Visible) - { - // 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)) - { - _isTextSetProgrammatically = true; - if (_viewModel.Results != null) - { - SearchBox.QueryTextBox.Text = MainViewModel.GetSearchText( - _viewModel.Results.SelectedIndex, - _viewModel.SystemQueryText, - _viewModel.QueryText); - } - } - } - - private void OnMouseDown(object sender, MouseButtonEventArgs e) - { - if (e.ChangedButton == MouseButton.Left) - { - DragMove(); - } - } - - private void InitializePosition() - { - Top = WindowTop(); - Left = WindowLeft(); - _settings.WindowTop = Top; - _settings.WindowLeft = Left; - } - - private void OnActivated(object sender, EventArgs e) - { - if (_settings.ClearInputOnLaunch) - { - _viewModel.ClearQueryCommand.Execute(null); - } - } - - private void OnDeactivated(object sender, EventArgs e) - { - if (_settings.HideWhenDeactivated) - { - // (this.FindResource("OutroStoryboard") as Storyboard).Begin(); - Hide(); - } - } - - private void UpdatePosition() - { - if (_settings.RememberLastLaunchLocation) - { - Left = _settings.WindowLeft; - Top = _settings.WindowTop; - } - else - { - Top = WindowTop(); - Left = WindowLeft(); - } - } - - private void OnLocationChanged(object sender, EventArgs e) - { - if (_settings.RememberLastLaunchLocation) - { - _settings.WindowLeft = Left; - _settings.WindowTop = Top; - } - } - - /// - /// Calculates X co-ordinate of main window top left corner. - /// - /// X co-ordinate of main window top left corner - private double WindowLeft() - { - var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); - var left = ((dip2.X - ActualWidth) / 2) + dip1.X; - return left; - } - - private double WindowTop() - { - var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); - var top = ((dip2.Y - SearchBox.ActualHeight) / 4) + dip1.Y; - return top; - } - - private void Launcher_KeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Tab && Keyboard.IsKeyDown(Key.LeftShift)) - { - _viewModel.SelectPrevTabItemCommand.Execute(null); - UpdateTextBoxToSelectedItem(); - e.Handled = true; - } - else if (e.Key == Key.Tab) - { - _viewModel.SelectNextTabItemCommand.Execute(null); - UpdateTextBoxToSelectedItem(); - e.Handled = true; - } - else if (e.Key == Key.Down) - { - _viewModel.SelectNextItemCommand.Execute(null); - UpdateTextBoxToSelectedItem(); - e.Handled = true; - } - else if (e.Key == Key.Up) - { - _viewModel.SelectPrevItemCommand.Execute(null); - UpdateTextBoxToSelectedItem(); - e.Handled = true; - } - else if (e.Key == Key.Right) - { - if (SearchBox.QueryTextBox.CaretIndex == SearchBox.QueryTextBox.Text.Length) - { - _viewModel.SelectNextContextMenuItemCommand.Execute(null); - e.Handled = true; - } - } - else if (e.Key == Key.Left) - { - if (SearchBox.QueryTextBox.CaretIndex == SearchBox.QueryTextBox.Text.Length) - { - if (_viewModel.Results != null && _viewModel.Results.IsContextMenuItemSelected()) - { - _viewModel.SelectPreviousContextMenuItemCommand.Execute(null); - e.Handled = true; - } - } - } - else if (e.Key == Key.PageDown) - { - _viewModel.SelectNextPageCommand.Execute(null); - e.Handled = true; - } - else if (e.Key == Key.PageUp) - { - _viewModel.SelectPrevPageCommand.Execute(null); - e.Handled = true; - } - else if (e.Key == Key.Back) - { - _deletePressed = true; - } - else - { - _viewModel.HandleContextMenu(e.Key, Keyboard.Modifiers); - } - } - - private void UpdateTextBoxToSelectedItem() - { - var itemText = _viewModel?.Results?.SelectedItem?.ToString() ?? null; - if (!string.IsNullOrEmpty(itemText)) - { - _viewModel.ChangeQueryText(itemText); - } - } - - private void SuggestionsList_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - ListView listview = (ListView)sender; - _viewModel.Results.SelectedItem = (ResultViewModel)listview.SelectedItem; - if (e.AddedItems.Count > 0 && e.AddedItems[0] != null) - { - listview.ScrollIntoView(e.AddedItems[0]); - } - - // 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 - if (_viewModel.Results != null) - { - SearchBox.AutoCompleteTextBlock.Text = MainViewModel.GetAutoCompleteText( - _viewModel.Results.SelectedIndex, - _viewModel.Results.SelectedItem?.ToString(), - _viewModel.QueryText); - } - } - - private bool disposedValue = false; - - private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e) - { - if (_isTextSetProgrammatically) - { - var textBox = (TextBox)sender; - textBox.SelectionStart = textBox.Text.Length; - _isTextSetProgrammatically = false; - } - else - { - var text = ((TextBox)sender).Text; - if (string.IsNullOrEmpty(text)) - { - SearchBox.AutoCompleteTextBlock.Text = string.Empty; - } - - _viewModel.QueryText = text; - _viewModel.Query(); - } - } - - private void ListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e) - { - if (e.ChangedButton == MouseButton.Right) - { - e.Handled = true; - } - } - - private void OnVisibilityChanged(object sender, DependencyPropertyChangedEventArgs e) - { - if (Visibility == Visibility.Visible) - { - _deletePressed = false; - if (_firstDeleteTimer != null) - { - _firstDeleteTimer.Start(); - } - - // (this.FindResource("IntroStoryboard") as Storyboard).Begin(); - SearchBox.QueryTextBox.Focus(); - Keyboard.Focus(SearchBox.QueryTextBox); - - _settings.ActivateTimes++; - - if (!string.IsNullOrEmpty(SearchBox.QueryTextBox.Text)) - { - SearchBox.QueryTextBox.SelectAll(); - } - - // Log the time taken from pressing the hotkey till launcher is visible as separate events depending on if it's the first hotkey invoke or second - if (!_coldStateHotkeyPressed) - { - PowerToysTelemetry.Log.WriteEvent(new LauncherColdStateHotkeyEvent() { HotkeyToVisibleTimeMs = _viewModel.GetHotkeyEventTimeMs() }); - _coldStateHotkeyPressed = true; - } - else - { - PowerToysTelemetry.Log.WriteEvent(new LauncherWarmStateHotkeyEvent() { HotkeyToVisibleTimeMs = _viewModel.GetHotkeyEventTimeMs() }); - } - } - else - { - if (_firstDeleteTimer != null) - { - _firstDeleteTimer.Stop(); - } - } - } - - private void OutroStoryboard_Completed(object sender, EventArgs e) - { - Hide(); - } - - private void SearchBox_UpdateFlowDirection() - { - SearchBox.QueryTextBox.FlowDirection = MainViewModel.GetLanguageFlowDirection(); - SearchBox.AutoCompleteTextBlock.FlowDirection = MainViewModel.GetLanguageFlowDirection(); - } - - private void SearchBox_InputLanguageChanged(object sender, InputLanguageEventArgs e) - { - SearchBox_UpdateFlowDirection(); - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - if (_firstDeleteTimer != null) - { - _firstDeleteTimer.Dispose(); - } - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - _firstDeleteTimer = null; - disposedValue = true; - } - } - - // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - // ~MainWindow() - // { - // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - // Dispose(disposing: false); - // } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} +// 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 System; +using System.ComponentModel; +using System.Timers; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Microsoft.PowerLauncher.Telemetry; +using Microsoft.PowerToys.Telemetry; +using PowerLauncher.Helper; +using PowerLauncher.ViewModel; +using Wox.Infrastructure.UserSettings; +using KeyEventArgs = System.Windows.Input.KeyEventArgs; +using Screen = System.Windows.Forms.Screen; + +namespace PowerLauncher +{ + public partial class MainWindow : IDisposable + { + private readonly Settings _settings; + private readonly MainViewModel _viewModel; + private bool _isTextSetProgrammatically; + private bool _deletePressed = false; + private Timer _firstDeleteTimer = new Timer(); + private bool _coldStateHotkeyPressed = false; + + public MainWindow(Settings settings, MainViewModel mainVM) + : this() + { + DataContext = mainVM; + _viewModel = mainVM; + _settings = settings; + + InitializeComponent(); + + _firstDeleteTimer.Elapsed += CheckForFirstDelete; + _firstDeleteTimer.Interval = 1000; + } + + private void CheckForFirstDelete(object sender, ElapsedEventArgs e) + { + if (_firstDeleteTimer != null) + { + _firstDeleteTimer.Stop(); + if (_deletePressed) + { + PowerToysTelemetry.Log.WriteEvent(new LauncherFirstDeleteEvent()); + } + } + } + + public MainWindow() + { + InitializeComponent(); + } + + private void OnClosing(object sender, CancelEventArgs e) + { + _viewModel.Save(); + } + + private void BringProcessToForeground() + { + // Use SendInput hack to allow Activate to work - required to resolve focus issue https://github.com/microsoft/PowerToys/issues/4270 + WindowsInteropHelper.INPUT input = new WindowsInteropHelper.INPUT { Type = WindowsInteropHelper.INPUTTYPE.INPUTMOUSE, Data = { } }; + WindowsInteropHelper.INPUT[] inputs = new WindowsInteropHelper.INPUT[] { input }; + + // Send empty mouse event. This makes this thread the last to send input, and hence allows it to pass foreground permission checks + _ = NativeMethods.SendInput(1, inputs, WindowsInteropHelper.INPUT.Size); + Activate(); + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + WindowsInteropHelper.DisableControlBox(this); + InitializePosition(); + + SearchBox.QueryTextBox.DataContext = _viewModel; + SearchBox.QueryTextBox.PreviewKeyDown += Launcher_KeyDown; + SearchBox.QueryTextBox.TextChanged += QueryTextBox_TextChanged; + + // Set initial language flow direction + SearchBox_UpdateFlowDirection(); + + // Register language changed event + InputLanguageManager.Current.InputLanguageChanged += SearchBox_InputLanguageChanged; + + SearchBox.QueryTextBox.Focus(); + + ListBox.DataContext = _viewModel; + ListBox.SuggestionsList.SelectionChanged += SuggestionsList_SelectionChanged; + ListBox.SuggestionsList.PreviewMouseLeftButtonUp += SuggestionsList_PreviewMouseLeftButtonUp; + _viewModel.PropertyChanged += ViewModel_PropertyChanged; + + BringProcessToForeground(); + } + + private void SuggestionsList_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + var result = ((FrameworkElement)e.OriginalSource).DataContext; + if (result != null) + { + // This may be null if the tapped item was one of the context buttons (run as admin etc). + if (result is ResultViewModel resultVM) + { + _viewModel.Results.SelectedItem = resultVM; + _viewModel.OpenResultCommand.Execute(null); + } + } + } + + private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(MainViewModel.MainWindowVisibility)) + { + if (Visibility == System.Windows.Visibility.Visible) + { + // 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)) + { + _isTextSetProgrammatically = true; + if (_viewModel.Results != null) + { + SearchBox.QueryTextBox.Text = MainViewModel.GetSearchText( + _viewModel.Results.SelectedIndex, + _viewModel.SystemQueryText, + _viewModel.QueryText); + } + } + } + + private void OnMouseDown(object sender, MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left) + { + DragMove(); + } + } + + private void InitializePosition() + { + Top = WindowTop(); + Left = WindowLeft(); + _settings.WindowTop = Top; + _settings.WindowLeft = Left; + } + + private void OnActivated(object sender, EventArgs e) + { + if (_settings.ClearInputOnLaunch) + { + _viewModel.ClearQueryCommand.Execute(null); + } + } + + private void OnDeactivated(object sender, EventArgs e) + { + if (_settings.HideWhenDeactivated) + { + // (this.FindResource("OutroStoryboard") as Storyboard).Begin(); + Hide(); + } + } + + private void UpdatePosition() + { + if (_settings.RememberLastLaunchLocation) + { + Left = _settings.WindowLeft; + Top = _settings.WindowTop; + } + else + { + Top = WindowTop(); + Left = WindowLeft(); + } + } + + private void OnLocationChanged(object sender, EventArgs e) + { + if (_settings.RememberLastLaunchLocation) + { + _settings.WindowLeft = Left; + _settings.WindowTop = Top; + } + } + + /// + /// Calculates X co-ordinate of main window top left corner. + /// + /// X co-ordinate of main window top left corner + private double WindowLeft() + { + var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); + var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); + var left = ((dip2.X - ActualWidth) / 2) + dip1.X; + return left; + } + + private double WindowTop() + { + var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); + var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); + var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); + var top = ((dip2.Y - SearchBox.ActualHeight) / 4) + dip1.Y; + return top; + } + + private void Launcher_KeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Tab && Keyboard.IsKeyDown(Key.LeftShift)) + { + _viewModel.SelectPrevTabItemCommand.Execute(null); + UpdateTextBoxToSelectedItem(); + e.Handled = true; + } + else if (e.Key == Key.Tab) + { + _viewModel.SelectNextTabItemCommand.Execute(null); + UpdateTextBoxToSelectedItem(); + e.Handled = true; + } + else if (e.Key == Key.Down) + { + _viewModel.SelectNextItemCommand.Execute(null); + UpdateTextBoxToSelectedItem(); + e.Handled = true; + } + else if (e.Key == Key.Up) + { + _viewModel.SelectPrevItemCommand.Execute(null); + UpdateTextBoxToSelectedItem(); + e.Handled = true; + } + else if (e.Key == Key.Right) + { + if (SearchBox.QueryTextBox.CaretIndex == SearchBox.QueryTextBox.Text.Length) + { + _viewModel.SelectNextContextMenuItemCommand.Execute(null); + e.Handled = true; + } + } + else if (e.Key == Key.Left) + { + if (SearchBox.QueryTextBox.CaretIndex == SearchBox.QueryTextBox.Text.Length) + { + if (_viewModel.Results != null && _viewModel.Results.IsContextMenuItemSelected()) + { + _viewModel.SelectPreviousContextMenuItemCommand.Execute(null); + e.Handled = true; + } + } + } + else if (e.Key == Key.PageDown) + { + _viewModel.SelectNextPageCommand.Execute(null); + e.Handled = true; + } + else if (e.Key == Key.PageUp) + { + _viewModel.SelectPrevPageCommand.Execute(null); + e.Handled = true; + } + else if (e.Key == Key.Back) + { + _deletePressed = true; + } + else + { + _viewModel.HandleContextMenu(e.Key, Keyboard.Modifiers); + } + } + + private void UpdateTextBoxToSelectedItem() + { + var itemText = _viewModel?.Results?.SelectedItem?.ToString() ?? null; + if (!string.IsNullOrEmpty(itemText)) + { + _viewModel.ChangeQueryText(itemText); + } + } + + private void SuggestionsList_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + ListView listview = (ListView)sender; + _viewModel.Results.SelectedItem = (ResultViewModel)listview.SelectedItem; + if (e.AddedItems.Count > 0 && e.AddedItems[0] != null) + { + listview.ScrollIntoView(e.AddedItems[0]); + } + + // 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 + if (_viewModel.Results != null) + { + SearchBox.AutoCompleteTextBlock.Text = MainViewModel.GetAutoCompleteText( + _viewModel.Results.SelectedIndex, + _viewModel.Results.SelectedItem?.ToString(), + _viewModel.QueryText); + } + } + + private bool disposedValue = false; + + private void QueryTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + if (_isTextSetProgrammatically) + { + var textBox = (TextBox)sender; + textBox.SelectionStart = textBox.Text.Length; + _isTextSetProgrammatically = false; + } + else + { + var text = ((TextBox)sender).Text; + if (string.IsNullOrEmpty(text)) + { + SearchBox.AutoCompleteTextBlock.Text = string.Empty; + } + + _viewModel.QueryText = text; + _viewModel.Query(); + } + } + + private void ListBox_PreviewMouseDown(object sender, MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Right) + { + e.Handled = true; + } + } + + private void OnVisibilityChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if (Visibility == Visibility.Visible) + { + _deletePressed = false; + if (_firstDeleteTimer != null) + { + _firstDeleteTimer.Start(); + } + + // (this.FindResource("IntroStoryboard") as Storyboard).Begin(); + SearchBox.QueryTextBox.Focus(); + Keyboard.Focus(SearchBox.QueryTextBox); + + _settings.ActivateTimes++; + + if (!string.IsNullOrEmpty(SearchBox.QueryTextBox.Text)) + { + SearchBox.QueryTextBox.SelectAll(); + } + + // Log the time taken from pressing the hotkey till launcher is visible as separate events depending on if it's the first hotkey invoke or second + if (!_coldStateHotkeyPressed) + { + PowerToysTelemetry.Log.WriteEvent(new LauncherColdStateHotkeyEvent() { HotkeyToVisibleTimeMs = _viewModel.GetHotkeyEventTimeMs() }); + _coldStateHotkeyPressed = true; + } + else + { + PowerToysTelemetry.Log.WriteEvent(new LauncherWarmStateHotkeyEvent() { HotkeyToVisibleTimeMs = _viewModel.GetHotkeyEventTimeMs() }); + } + } + else + { + if (_firstDeleteTimer != null) + { + _firstDeleteTimer.Stop(); + } + } + } + + private void OutroStoryboard_Completed(object sender, EventArgs e) + { + Hide(); + } + + private void SearchBox_UpdateFlowDirection() + { + SearchBox.QueryTextBox.FlowDirection = MainViewModel.GetLanguageFlowDirection(); + SearchBox.AutoCompleteTextBlock.FlowDirection = MainViewModel.GetLanguageFlowDirection(); + } + + private void SearchBox_InputLanguageChanged(object sender, InputLanguageEventArgs e) + { + SearchBox_UpdateFlowDirection(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + if (_firstDeleteTimer != null) + { + _firstDeleteTimer.Dispose(); + } + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + _firstDeleteTimer = null; + disposedValue = true; + } + } + + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + // ~MainWindow() + // { + // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + // Dispose(disposing: false); + // } + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/modules/launcher/PowerLauncher/PowerLauncher.csproj b/src/modules/launcher/PowerLauncher/PowerLauncher.csproj index 7e8b388383..63b774ffd0 100644 --- a/src/modules/launcher/PowerLauncher/PowerLauncher.csproj +++ b/src/modules/launcher/PowerLauncher/PowerLauncher.csproj @@ -259,7 +259,7 @@ Designer - + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/SettingsWatcher.cs b/src/modules/launcher/PowerLauncher/SettingsWatcher.cs index 3ed7d9f6a7..9c09693b5a 100644 --- a/src/modules/launcher/PowerLauncher/SettingsWatcher.cs +++ b/src/modules/launcher/PowerLauncher/SettingsWatcher.cs @@ -1,119 +1,119 @@ -// 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 System; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Windows.Input; -using Microsoft.PowerToys.Settings.UI.Lib; -using Wox.Core.Plugin; -using Wox.Infrastructure.Hotkey; -using Wox.Infrastructure.UserSettings; -using Wox.Plugin; - -namespace PowerLauncher -{ - // Watch for /Local/Microsoft/PowerToys/Launcher/Settings.json changes - public class SettingsWatcher : BaseModel - { - private static int _maxRetries = 10; - private static object _watcherSyncObject = new object(); - private FileSystemWatcher _watcher; - private Settings _settings; - - public SettingsWatcher(Settings settings) - { - _settings = settings; - - // Set up watcher - _watcher = Microsoft.PowerToys.Settings.UI.Lib.Utilities.Helper.GetFileWatcher(PowerLauncherSettings.ModuleName, "settings.json", OverloadSettings); - - // Load initial settings file - OverloadSettings(); - } - - public void OverloadSettings() - { - Monitor.Enter(_watcherSyncObject); - var retry = true; - var retryCount = 0; - while (retry) - { - try - { - retryCount++; - if (!SettingsUtils.SettingsExists(PowerLauncherSettings.ModuleName)) - { - Debug.WriteLine("PT Run settings.json was missing, creating a new one"); - - var defaultSettings = new PowerLauncherSettings(); - defaultSettings.Save(); - } - - var overloadSettings = SettingsUtils.GetSettings(PowerLauncherSettings.ModuleName); - - var openPowerlauncher = ConvertHotkey(overloadSettings.Properties.OpenPowerLauncher); - if (_settings.Hotkey != openPowerlauncher) - { - _settings.Hotkey = openPowerlauncher; - } - - var shell = PluginManager.AllPlugins.Find(pp => pp.Metadata.Name == "Shell"); - if (shell != null) - { - var shellSettings = shell.Plugin as ISettingProvider; - shellSettings.UpdateSettings(overloadSettings); - } - - if (_settings.MaxResultsToShow != overloadSettings.Properties.MaximumNumberOfResults) - { - _settings.MaxResultsToShow = overloadSettings.Properties.MaximumNumberOfResults; - } - - if (_settings.IgnoreHotkeysOnFullscreen != overloadSettings.Properties.IgnoreHotkeysInFullscreen) - { - _settings.IgnoreHotkeysOnFullscreen = overloadSettings.Properties.IgnoreHotkeysInFullscreen; - } - - var indexer = PluginManager.AllPlugins.Find(p => p.Metadata.Name.Equals("Windows Indexer Plugin", StringComparison.OrdinalIgnoreCase)); - if (indexer != null) - { - var indexerSettings = indexer.Plugin as ISettingProvider; - indexerSettings.UpdateSettings(overloadSettings); - } - - if (_settings.ClearInputOnLaunch != overloadSettings.Properties.ClearInputOnLaunch) - { - _settings.ClearInputOnLaunch = overloadSettings.Properties.ClearInputOnLaunch; - } - - retry = false; - } - - // the settings application can hold a lock on the settings.json file which will result in a IOException. - // This should be changed to properly synch with the settings app instead of retrying. - catch (IOException e) - { - if (retryCount > _maxRetries) - { - retry = false; - } - - Thread.Sleep(1000); - Debug.WriteLine(e.Message); - } - } - - Monitor.Exit(_watcherSyncObject); - } - - private static string ConvertHotkey(HotkeySettings hotkey) - { - Key key = KeyInterop.KeyFromVirtualKey(hotkey.Code); - HotkeyModel model = new HotkeyModel(hotkey.Alt, hotkey.Shift, hotkey.Win, hotkey.Ctrl, key); - return model.ToString(); - } - } -} +// 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 System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Windows.Input; +using Microsoft.PowerToys.Settings.UI.Lib; +using Wox.Core.Plugin; +using Wox.Infrastructure.Hotkey; +using Wox.Infrastructure.UserSettings; +using Wox.Plugin; + +namespace PowerLauncher +{ + // Watch for /Local/Microsoft/PowerToys/Launcher/Settings.json changes + public class SettingsWatcher : BaseModel + { + private const int MaxRetries = 10; + private static readonly object _watcherSyncObject = new object(); + private readonly FileSystemWatcher _watcher; + private readonly Settings _settings; + + public SettingsWatcher(Settings settings) + { + _settings = settings; + + // Set up watcher + _watcher = Microsoft.PowerToys.Settings.UI.Lib.Utilities.Helper.GetFileWatcher(PowerLauncherSettings.ModuleName, "settings.json", OverloadSettings); + + // Load initial settings file + OverloadSettings(); + } + + public void OverloadSettings() + { + Monitor.Enter(_watcherSyncObject); + var retry = true; + var retryCount = 0; + while (retry) + { + try + { + retryCount++; + if (!SettingsUtils.SettingsExists(PowerLauncherSettings.ModuleName)) + { + Debug.WriteLine("PT Run settings.json was missing, creating a new one"); + + var defaultSettings = new PowerLauncherSettings(); + defaultSettings.Save(); + } + + var overloadSettings = SettingsUtils.GetSettings(PowerLauncherSettings.ModuleName); + + var openPowerlauncher = ConvertHotkey(overloadSettings.Properties.OpenPowerLauncher); + if (_settings.Hotkey != openPowerlauncher) + { + _settings.Hotkey = openPowerlauncher; + } + + var shell = PluginManager.AllPlugins.Find(pp => pp.Metadata.Name == "Shell"); + if (shell != null) + { + var shellSettings = shell.Plugin as ISettingProvider; + shellSettings.UpdateSettings(overloadSettings); + } + + if (_settings.MaxResultsToShow != overloadSettings.Properties.MaximumNumberOfResults) + { + _settings.MaxResultsToShow = overloadSettings.Properties.MaximumNumberOfResults; + } + + if (_settings.IgnoreHotkeysOnFullscreen != overloadSettings.Properties.IgnoreHotkeysInFullscreen) + { + _settings.IgnoreHotkeysOnFullscreen = overloadSettings.Properties.IgnoreHotkeysInFullscreen; + } + + var indexer = PluginManager.AllPlugins.Find(p => p.Metadata.Name.Equals("Windows Indexer Plugin", StringComparison.OrdinalIgnoreCase)); + if (indexer != null) + { + var indexerSettings = indexer.Plugin as ISettingProvider; + indexerSettings.UpdateSettings(overloadSettings); + } + + if (_settings.ClearInputOnLaunch != overloadSettings.Properties.ClearInputOnLaunch) + { + _settings.ClearInputOnLaunch = overloadSettings.Properties.ClearInputOnLaunch; + } + + retry = false; + } + + // the settings application can hold a lock on the settings.json file which will result in a IOException. + // This should be changed to properly synch with the settings app instead of retrying. + catch (IOException e) + { + if (retryCount > MaxRetries) + { + retry = false; + } + + Thread.Sleep(1000); + Debug.WriteLine(e.Message); + } + } + + Monitor.Exit(_watcherSyncObject); + } + + private static string ConvertHotkey(HotkeySettings hotkey) + { + Key key = KeyInterop.KeyFromVirtualKey(hotkey.Code); + HotkeyModel model = new HotkeyModel(hotkey.Alt, hotkey.Shift, hotkey.Win, hotkey.Ctrl, key); + return model.ToString(); + } + } +} diff --git a/src/modules/launcher/PowerLauncher/Storage/QueryHistory.cs b/src/modules/launcher/PowerLauncher/Storage/QueryHistory.cs index 24d9575301..54c1f7f382 100644 --- a/src/modules/launcher/PowerLauncher/Storage/QueryHistory.cs +++ b/src/modules/launcher/PowerLauncher/Storage/QueryHistory.cs @@ -12,7 +12,7 @@ namespace PowerLauncher.Storage { public List Items { get; } = new List(); - private int _maxHistory = 300; + private readonly int _maxHistory = 300; public void Add(string query) { diff --git a/src/modules/launcher/PowerLauncher/Storage/UserSelectedRecord.cs b/src/modules/launcher/PowerLauncher/Storage/UserSelectedRecord.cs index daed2e9b96..f5e4a62ec1 100644 --- a/src/modules/launcher/PowerLauncher/Storage/UserSelectedRecord.cs +++ b/src/modules/launcher/PowerLauncher/Storage/UserSelectedRecord.cs @@ -12,7 +12,7 @@ namespace PowerLauncher.Storage public class UserSelectedRecord { [JsonProperty] - private Dictionary records = new Dictionary(); + private readonly Dictionary records = new Dictionary(); public void Add(Result result) { diff --git a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs index a178fd3c9c..133b28f56f 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs @@ -29,7 +29,7 @@ namespace PowerLauncher.ViewModel { public class MainViewModel : BaseModel, ISavable, IDisposable { - private static Query _emptyQuery = new Query(); + private static readonly Query _emptyQuery = new Query(); private static bool _disposed; private readonly WoxJsonStorage _historyItemsStorage; @@ -41,6 +41,7 @@ namespace PowerLauncher.ViewModel private readonly TopMostRecord _topMostRecord; private readonly object _addResultsLock = new object(); private readonly Internationalization _translator = InternationalizationManager.Instance; + private readonly System.Diagnostics.Stopwatch _hotkeyTimer = new System.Diagnostics.Stopwatch(); private Query _currentQuery; private string _queryTextBeforeLeaveResults; @@ -49,15 +50,13 @@ namespace PowerLauncher.ViewModel private CancellationToken _updateToken; private bool _saved; - - private HotkeyManager _hotkeyManager { get; set; } - private ushort _hotkeyHandle; - private System.Diagnostics.Stopwatch hotkeyTimer = new System.Diagnostics.Stopwatch(); + + internal HotkeyManager HotkeyManager { get; set; } public MainViewModel(Settings settings) { - _hotkeyManager = new HotkeyManager(); + HotkeyManager = new HotkeyManager(); _saved = false; _queryTextBeforeLeaveResults = string.Empty; _currentQuery = _emptyQuery; @@ -88,7 +87,7 @@ namespace PowerLauncher.ViewModel { if (!string.IsNullOrEmpty(_settings.PreviousHotkey)) { - _hotkeyManager.UnregisterHotkey(_hotkeyHandle); + HotkeyManager.UnregisterHotkey(_hotkeyHandle); } if (!string.IsNullOrEmpty(_settings.Hotkey)) @@ -603,12 +602,6 @@ namespace PowerLauncher.ViewModel return selected; } - private bool ContextMenuSelected() - { - var selected = SelectedResults == ContextMenu; - return selected; - } - private bool HistorySelected() { var selected = SelectedResults == History; @@ -632,10 +625,10 @@ namespace PowerLauncher.ViewModel Shift = hotkeyModel.Shift, Ctrl = hotkeyModel.Ctrl, Win = hotkeyModel.Win, - Key = (byte)KeyInterop.VirtualKeyFromKey(hotkeyModel.CharKey) + Key = (byte)KeyInterop.VirtualKeyFromKey(hotkeyModel.CharKey), }; - _hotkeyHandle = _hotkeyManager.RegisterHotkey(hotkey, action); + _hotkeyHandle = HotkeyManager.RegisterHotkey(hotkey, action); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception) @@ -799,7 +792,7 @@ namespace PowerLauncher.ViewModel { if (!plugin.Metadata.Disabled && plugin.Metadata.Name != "Window Walker") { - var _ = PluginManager.QueryForPlugin(plugin, query); + _ = PluginManager.QueryForPlugin(plugin, query); } } } @@ -877,10 +870,10 @@ namespace PowerLauncher.ViewModel { if (_hotkeyHandle != 0) { - _hotkeyManager?.UnregisterHotkey(_hotkeyHandle); + HotkeyManager?.UnregisterHotkey(_hotkeyHandle); } - _hotkeyManager?.Dispose(); + HotkeyManager?.Dispose(); _updateSource?.Dispose(); _disposed = true; } @@ -895,16 +888,16 @@ namespace PowerLauncher.ViewModel public void StartHotkeyTimer() { - hotkeyTimer.Start(); + _hotkeyTimer.Start(); } public long GetHotkeyEventTimeMs() { - hotkeyTimer.Stop(); - long recordedTime = hotkeyTimer.ElapsedMilliseconds; + _hotkeyTimer.Stop(); + long recordedTime = _hotkeyTimer.ElapsedMilliseconds; // Reset the stopwatch and return the time elapsed - hotkeyTimer.Reset(); + _hotkeyTimer.Reset(); return recordedTime; } } diff --git a/src/modules/launcher/PowerLauncher/ViewModel/RelayCommand.cs b/src/modules/launcher/PowerLauncher/ViewModel/RelayCommand.cs index af6780255f..b4a24abffe 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/RelayCommand.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/RelayCommand.cs @@ -9,7 +9,7 @@ namespace PowerLauncher.ViewModel { public class RelayCommand : ICommand { - private Action _action; + private readonly Action _action; public RelayCommand(Action action) {