From c816e1faf35dc63c108df38ff90f350c0afdacc6 Mon Sep 17 00:00:00 2001 From: HellBrick Date: Sat, 17 Jan 2015 14:45:16 +0300 Subject: [PATCH 01/18] Added SystemWindow.IsClosed() --- ManagedWinapi/SystemWindow.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ManagedWinapi/SystemWindow.cs b/ManagedWinapi/SystemWindow.cs index d72ed7f8..84786e14 100644 --- a/ManagedWinapi/SystemWindow.cs +++ b/ManagedWinapi/SystemWindow.cs @@ -595,6 +595,22 @@ public bool Enabled } } + private bool _isClosed = false; + public bool IsClosed + { + get + { + _isClosed = _isClosed || GetClassNameFails(); + return _isClosed; + } + } + + private bool GetClassNameFails() + { + StringBuilder builder = new StringBuilder( 2 ); + return GetClassName( HWnd, builder, builder.Capacity ) == 0; + } + /// /// Returns or sets the visibility flag. /// From 90cd4c25c672184317d0431c800d315aa6f68ee3 Mon Sep 17 00:00:00 2001 From: HellBrick Date: Sat, 17 Jan 2015 14:45:16 +0300 Subject: [PATCH 02/18] The focus is no longer switched to the closed window --- Switcheroo/MainWindow.xaml.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Switcheroo/MainWindow.xaml.cs b/Switcheroo/MainWindow.xaml.cs index 786a6ccf..04eeb2ce 100644 --- a/Switcheroo/MainWindow.xaml.cs +++ b/Switcheroo/MainWindow.xaml.cs @@ -459,13 +459,11 @@ private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) private void CloseWindow(object sender, ExecutedRoutedEventArgs e) { if (lb.Items.Count > 0) - { - HideWindow(); + { var win = (AppWindow)lb.SelectedItem; if (win != null) { win.PostClose(); - win.SwitchTo(); } } else From f882b725e960eff9f7fef774f4c9a919dca6bafd Mon Sep 17 00:00:00 2001 From: HellBrick Date: Sat, 17 Jan 2015 14:45:16 +0300 Subject: [PATCH 03/18] Binding to ObservableCollection<> instead of List<> to allow removing items --- Switcheroo/MainWindow.xaml.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Switcheroo/MainWindow.xaml.cs b/Switcheroo/MainWindow.xaml.cs index 04eeb2ce..5e027c33 100644 --- a/Switcheroo/MainWindow.xaml.cs +++ b/Switcheroo/MainWindow.xaml.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; using System.Net; @@ -44,7 +45,7 @@ namespace Switcheroo { public partial class MainWindow : Window { - private List _windowList; + private ObservableCollection _windowList; private NotifyIcon _notifyIcon; private HotKey _hotkey; @@ -195,7 +196,7 @@ private static async Task GetLatestVersion() /// private void LoadData() { - _windowList = new WindowFinder().GetWindows(); + _windowList = new ObservableCollection( new WindowFinder().GetWindows() ); foreach (var window in _windowList) { From f0ebd5f02224f4a25ce3764540106bad98dd3ebc Mon Sep 17 00:00:00 2001 From: HellBrick Date: Sat, 17 Jan 2015 15:37:13 +0300 Subject: [PATCH 04/18] Added a proper AppWindowViewModel --- Switcheroo/AppWindowViewModel.cs | 41 ++ Switcheroo/MainWindow.xaml.cs | 1052 +++++++++++++++--------------- Switcheroo/Switcheroo.csproj | 438 +++++++------ Switcheroo/packages.config | 8 +- 4 files changed, 796 insertions(+), 743 deletions(-) create mode 100644 Switcheroo/AppWindowViewModel.cs diff --git a/Switcheroo/AppWindowViewModel.cs b/Switcheroo/AppWindowViewModel.cs new file mode 100644 index 00000000..742f328f --- /dev/null +++ b/Switcheroo/AppWindowViewModel.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Caliburn.Micro; +using Switcheroo.Core; + +namespace Switcheroo +{ + public class AppWindowViewModel: PropertyChangedBase + { + public AppWindowViewModel( AppWindow appWindow ) + { + AppWindow = appWindow; + } + + public AppWindow AppWindow { get; private set; } + + #region Bindable properties + + public IntPtr HWnd + { + get { return AppWindow.HWnd; } + } + + public string FormattedTitle + { + get { return AppWindow.FormattedTitle; } + set { AppWindow.FormattedTitle = value; NotifyOfPropertyChange( () => FormattedTitle ); } + } + + public string FormattedProcessTitle + { + get { return AppWindow.FormattedProcessTitle; } + set { AppWindow.FormattedProcessTitle = value; NotifyOfPropertyChange( () => FormattedProcessTitle ); } + } + + #endregion + } +} \ No newline at end of file diff --git a/Switcheroo/MainWindow.xaml.cs b/Switcheroo/MainWindow.xaml.cs index 5e027c33..e84a7ddc 100644 --- a/Switcheroo/MainWindow.xaml.cs +++ b/Switcheroo/MainWindow.xaml.cs @@ -1,526 +1,526 @@ -/* - * Switcheroo - The incremental-search task switcher for Windows. - * http://www.switcheroo.io/ - * Copyright 2009, 2010 James Sulak - * Copyright 2014 Regin Larsen - * - * Switcheroo is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Switcheroo is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Switcheroo. If not, see . - */ - -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Forms; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Threading; -using ManagedWinapi; -using ManagedWinapi.Windows; -using Switcheroo.Core; -using Switcheroo.Core.Matchers; -using Switcheroo.Properties; -using Application = System.Windows.Application; -using MenuItem = System.Windows.Forms.MenuItem; -using MessageBox = System.Windows.MessageBox; - -namespace Switcheroo -{ - public partial class MainWindow : Window - { - private ObservableCollection _windowList; - private NotifyIcon _notifyIcon; - private HotKey _hotkey; - - public readonly static RoutedUICommand CloseWindowCommand = new RoutedUICommand(); - public readonly static RoutedUICommand SwitchToWindowCommand = new RoutedUICommand(); - public readonly static RoutedUICommand ScrollListDownCommand = new RoutedUICommand(); - public readonly static RoutedUICommand ScrollListUpCommand = new RoutedUICommand(); - private OptionsWindow _optionsWindow; - private AboutWindow _aboutWindow; - private AltTabHook _altTabHook; - - public MainWindow() - { - InitializeComponent(); - - SetUpNotifyIcon(); - - SetUpHotKey(); - - SetUpAltTabHook(); - - CheckForUpdates(); - - Opacity = 0; - } - - /// ================================= - #region Private Methods - /// ================================= - private void SetUpHotKey() - { - _hotkey = new HotKey(); - _hotkey.LoadSettings(); - - Application.Current.Properties["hotkey"] = _hotkey; - - _hotkey.HotkeyPressed += hotkey_HotkeyPressed; - try - { - _hotkey.Enabled = true; - } - catch (HotkeyAlreadyInUseException) - { - var boxText = "The current hotkey for activating Switcheroo is in use by another program." + - Environment.NewLine + - Environment.NewLine + - "You can change the hotkey by right-clicking the Switcheroo icon in the system tray and choosing 'Options'."; - MessageBox.Show(boxText, "Hotkey already in use", MessageBoxButton.OK, MessageBoxImage.Warning); - } - } - - private void SetUpAltTabHook() - { - _altTabHook = new AltTabHook(); - _altTabHook.Pressed += AltTabPressed; - } - - private void SetUpNotifyIcon() - { - var icon = Properties.Resources.icon; - - var runOnStartupMenuItem = new MenuItem("Run on Startup", (s, e) => RunOnStartup(s as MenuItem)) - { - Checked = new AutoStart().IsEnabled - }; - - _notifyIcon = new NotifyIcon - { - Text = "Switcheroo", - Icon = icon, - Visible = true, - ContextMenu = new System.Windows.Forms.ContextMenu(new[] - { - new MenuItem("Options", (s, e) => Options()), - runOnStartupMenuItem, - new MenuItem("About", (s, e) => About()), - new MenuItem("Exit", (s, e) => Quit()) - }) - }; - } - - private static void RunOnStartup(MenuItem menuItem) - { - try - { - var autoStart = new AutoStart - { - IsEnabled = !menuItem.Checked - }; - menuItem.Checked = autoStart.IsEnabled; - } - catch (AutoStartException e) - { - MessageBox.Show(e.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Warning); - } - } - - private static void CheckForUpdates() - { - var timer = new DispatcherTimer(); - - timer.Tick += async (sender, args) => - { - timer.Stop(); - var latestVersion = await GetLatestVersion(); - var currentVersion = Assembly.GetEntryAssembly().GetName().Version; - if (latestVersion != null && latestVersion > currentVersion) - { - var result = MessageBox.Show( - string.Format("Switcheroo v{0} is available (you have v{1}).\r\n\r\nDo you want to download it?", - latestVersion, currentVersion), - "Update Available", MessageBoxButton.YesNo, MessageBoxImage.Information); - if (result == MessageBoxResult.Yes) - { - Process.Start("https://github.com/kvakulo/Switcheroo/releases/latest"); - } - } - else - { - timer.Interval = new TimeSpan(24, 0, 0); - timer.Start(); - } - }; - - timer.Interval = new TimeSpan(0, 5, 0); - timer.Start(); - } - - private static async Task GetLatestVersion() - { - try - { - var versionAsString = await new WebClient().DownloadStringTaskAsync("https://raw.github.com/kvakulo/Switcheroo/update/version.txt"); - Version newVersion; - if (Version.TryParse(versionAsString, out newVersion)) - { - return newVersion; - } - } - catch (WebException) - { - } - return null; - } - - /// - /// Populates the window list with the current running windows. - /// - private void LoadData() - { - _windowList = new ObservableCollection( new WindowFinder().GetWindows() ); - - foreach (var window in _windowList) - { - window.FormattedTitle = new XamlHighlighter().Highlight(new[] { new StringPart(window.Title) }); - window.FormattedProcessTitle = new XamlHighlighter().Highlight(new[] { new StringPart(window.ProcessTitle) }); - } - - lb.DataContext = null; - lb.DataContext = _windowList; - lb.SelectedIndex = 0; - tb.Clear(); - tb.Focus(); - Resize(); - } - - /// - /// Resizes window to match width and height of list. - /// - private void Resize() - { - // These two lines size upon load, but don't whiplash resize during typing - SizeToContent = SizeToContent.WidthAndHeight; - SizeToContent = SizeToContent.Manual; - Left = (SystemParameters.PrimaryScreenWidth / 2) - (ActualWidth / 2); - Top = (SystemParameters.PrimaryScreenHeight / 2) - (ActualHeight / 2); - } - - /// - /// Switches the window associated with the selected item. - /// - private void Switch() - { - if (lb.Items.Count > 0) - { - var win = (AppWindow) (lb.SelectedItem ?? lb.Items[0]); - win.SwitchTo(); - } - HideWindow(); - } - - private void HideWindow() - { - Opacity = 0; - - // Avoid flicker by delaying the "Hide" a bit. This makes sure - // that "Opacity = 0" is taking effect before the window is hidden. - var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) }; - timer.Tick += (sender, args) => - { - Hide(); - timer.Stop(); - }; - timer.Start(); - } - - #endregion - - - /// ================================= - #region Right-click menu functions - /// ================================= - - /// - /// Show Options dialog. - /// - private void Options() - { - if(_optionsWindow == null) - { - _optionsWindow = new OptionsWindow - { - WindowStartupLocation = WindowStartupLocation.CenterScreen - }; - _optionsWindow.Closed += (sender, args) => _optionsWindow = null; - _optionsWindow.ShowDialog(); - } - else - { - _optionsWindow.Activate(); - } - } - - /// - /// Show About dialog. - /// - private void About() - { - if (_aboutWindow == null) - { - _aboutWindow = new AboutWindow - { - WindowStartupLocation = WindowStartupLocation.CenterScreen - }; - _aboutWindow.Closed += (sender, args) => _aboutWindow = null; - _aboutWindow.ShowDialog(); - - } - else - { - _aboutWindow.Activate(); - } - } - - /// - /// Quit Switcheroo - /// - private void Quit() - { - _notifyIcon.Dispose(); - _notifyIcon = null; - _hotkey.Dispose(); - Application.Current.Shutdown(); - } - - #endregion - - - /// ================================= - #region Event Handlers - /// ================================= - - private void OnClose(object sender, System.ComponentModel.CancelEventArgs e) - { - e.Cancel = true; - HideWindow(); - } - - private void hotkey_HotkeyPressed(object sender, EventArgs e) - { - if (Visibility != Visibility.Visible) - { - Show(); - Activate(); - Keyboard.Focus(tb); - LoadData(); - Opacity = 1; - } - else - { - HideWindow(); - } - } - - private void AltTabPressed(object sender, AltTabHookEventArgs e) - { - if (!Settings.Default.AltTabHook) - { - // Ignore Alt+Tab presses if the hook is not activated by the user - return; - } - - e.Handled = true; - - if (Visibility != Visibility.Visible) - { - ActivateAndFocusMainWindow(); - - Keyboard.Focus(tb); - LoadData(); - - if (e.ShiftDown) - { - lb.SelectedIndex = lb.Items.Count - 1; - } - else - { - lb.SelectedIndex = 1; - } - - Opacity = 1; - - } - else - { - if (e.ShiftDown) - { - PreviousItem(); - } - else - { - NextItem(); - } - } - } - - private void ActivateAndFocusMainWindow() - { - // What happens below looks a bit weird, but for Switcheroo to get focus when using the Alt+Tab hook, - // it is needed to simulate an Alt keypress will bring Switcheroo to the foreground. Otherwise Switcheroo - // will become the foreground window, but the previous window will retain focus, and receive keep getting - // the keyboard input. - // http://www.codeproject.com/Tips/76427/How-to-bring-window-to-top-with-SetForegroundWindo - - var thisWindowHandle = new WindowInteropHelper(this).Handle; - var thisWindow = new AppWindow(thisWindowHandle); - - var altKey = new KeyboardKey(Keys.Alt); - var altKeyPressed = false; - - // Press the Alt key if it is not already being pressed - if ((altKey.AsyncState & 0x8000) == 0) - { - altKey.Press(); - altKeyPressed = true; - } - - // Bring the Switcheroo window to the foreground - Show(); - SystemWindow.ForegroundWindow = thisWindow; - Activate(); - - // Release the Alt key if it was pressed above - if (altKeyPressed) - { - altKey.Release(); - } - } - - private void TextChanged(object sender, TextChangedEventArgs args) - { - var text = tb.Text; - - var filterResults = new WindowFilterer().Filter(_windowList, text).ToList(); - - foreach (var filterResult in filterResults) - { - filterResult.AppWindow.FormattedTitle = GetFormattedTitleFromBestResult(filterResult.WindowTitleMatchResults); - filterResult.AppWindow.FormattedProcessTitle = GetFormattedTitleFromBestResult(filterResult.ProcessTitleMatchResults); - } - - lb.DataContext = filterResults.Select(r => r.AppWindow); - if (lb.Items.Count > 0) - { - lb.SelectedItem = lb.Items[0]; - } - } - - private static string GetFormattedTitleFromBestResult(IList matchResults) - { - var bestResult = matchResults.FirstOrDefault(r => r.Matched) ?? matchResults.First(); - return new XamlHighlighter().Highlight(bestResult.StringParts); - } - - private void Hide(object sender, ExecutedRoutedEventArgs e) - { - HideWindow(); - } - - private void OnEnterPressed(object sender, ExecutedRoutedEventArgs e) - { - Switch(); - e.Handled = true; - } - - private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) - { - Switch(); - e.Handled = true; - } - - private void CloseWindow(object sender, ExecutedRoutedEventArgs e) - { - if (lb.Items.Count > 0) - { - var win = (AppWindow)lb.SelectedItem; - if (win != null) - { - win.PostClose(); - } - } - else - { - HideWindow(); - } - e.Handled = true; - } - - private void ScrollListUp(object sender, ExecutedRoutedEventArgs e) - { - PreviousItem(); - e.Handled = true; - } - - private void PreviousItem() - { - if (lb.Items.Count > 0) - { - if (lb.SelectedIndex != 0) - { - lb.SelectedIndex--; - } - else - { - lb.SelectedIndex = lb.Items.Count - 1; - } - } - } - - private void ScrollListDown(object sender, ExecutedRoutedEventArgs e) - { - NextItem(); - e.Handled = true; - } - - private void NextItem() - { - if (lb.Items.Count > 0) - { - if (lb.SelectedIndex != lb.Items.Count - 1) - { - lb.SelectedIndex++; - } - else - { - lb.SelectedIndex = 0; - } - } - } - - private void MainWindow_OnLostFocus(object sender, EventArgs e) - { - HideWindow(); - } - - #endregion - } -} +/* + * Switcheroo - The incremental-search task switcher for Windows. + * http://www.switcheroo.io/ + * Copyright 2009, 2010 James Sulak + * Copyright 2014 Regin Larsen + * + * Switcheroo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Switcheroo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Switcheroo. If not, see . + */ + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Forms; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Threading; +using ManagedWinapi; +using ManagedWinapi.Windows; +using Switcheroo.Core; +using Switcheroo.Core.Matchers; +using Switcheroo.Properties; +using Application = System.Windows.Application; +using MenuItem = System.Windows.Forms.MenuItem; +using MessageBox = System.Windows.MessageBox; + +namespace Switcheroo +{ + public partial class MainWindow : Window + { + private ObservableCollection _windowList; + private NotifyIcon _notifyIcon; + private HotKey _hotkey; + + public readonly static RoutedUICommand CloseWindowCommand = new RoutedUICommand(); + public readonly static RoutedUICommand SwitchToWindowCommand = new RoutedUICommand(); + public readonly static RoutedUICommand ScrollListDownCommand = new RoutedUICommand(); + public readonly static RoutedUICommand ScrollListUpCommand = new RoutedUICommand(); + private OptionsWindow _optionsWindow; + private AboutWindow _aboutWindow; + private AltTabHook _altTabHook; + + public MainWindow() + { + InitializeComponent(); + + SetUpNotifyIcon(); + + SetUpHotKey(); + + SetUpAltTabHook(); + + CheckForUpdates(); + + Opacity = 0; + } + + /// ================================= + #region Private Methods + /// ================================= + private void SetUpHotKey() + { + _hotkey = new HotKey(); + _hotkey.LoadSettings(); + + Application.Current.Properties["hotkey"] = _hotkey; + + _hotkey.HotkeyPressed += hotkey_HotkeyPressed; + try + { + _hotkey.Enabled = true; + } + catch (HotkeyAlreadyInUseException) + { + var boxText = "The current hotkey for activating Switcheroo is in use by another program." + + Environment.NewLine + + Environment.NewLine + + "You can change the hotkey by right-clicking the Switcheroo icon in the system tray and choosing 'Options'."; + MessageBox.Show(boxText, "Hotkey already in use", MessageBoxButton.OK, MessageBoxImage.Warning); + } + } + + private void SetUpAltTabHook() + { + _altTabHook = new AltTabHook(); + _altTabHook.Pressed += AltTabPressed; + } + + private void SetUpNotifyIcon() + { + var icon = Properties.Resources.icon; + + var runOnStartupMenuItem = new MenuItem("Run on Startup", (s, e) => RunOnStartup(s as MenuItem)) + { + Checked = new AutoStart().IsEnabled + }; + + _notifyIcon = new NotifyIcon + { + Text = "Switcheroo", + Icon = icon, + Visible = true, + ContextMenu = new System.Windows.Forms.ContextMenu(new[] + { + new MenuItem("Options", (s, e) => Options()), + runOnStartupMenuItem, + new MenuItem("About", (s, e) => About()), + new MenuItem("Exit", (s, e) => Quit()) + }) + }; + } + + private static void RunOnStartup(MenuItem menuItem) + { + try + { + var autoStart = new AutoStart + { + IsEnabled = !menuItem.Checked + }; + menuItem.Checked = autoStart.IsEnabled; + } + catch (AutoStartException e) + { + MessageBox.Show(e.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Warning); + } + } + + private static void CheckForUpdates() + { + var timer = new DispatcherTimer(); + + timer.Tick += async (sender, args) => + { + timer.Stop(); + var latestVersion = await GetLatestVersion(); + var currentVersion = Assembly.GetEntryAssembly().GetName().Version; + if (latestVersion != null && latestVersion > currentVersion) + { + var result = MessageBox.Show( + string.Format("Switcheroo v{0} is available (you have v{1}).\r\n\r\nDo you want to download it?", + latestVersion, currentVersion), + "Update Available", MessageBoxButton.YesNo, MessageBoxImage.Information); + if (result == MessageBoxResult.Yes) + { + Process.Start("https://github.com/kvakulo/Switcheroo/releases/latest"); + } + } + else + { + timer.Interval = new TimeSpan(24, 0, 0); + timer.Start(); + } + }; + + timer.Interval = new TimeSpan(0, 5, 0); + timer.Start(); + } + + private static async Task GetLatestVersion() + { + try + { + var versionAsString = await new WebClient().DownloadStringTaskAsync("https://raw.github.com/kvakulo/Switcheroo/update/version.txt"); + Version newVersion; + if (Version.TryParse(versionAsString, out newVersion)) + { + return newVersion; + } + } + catch (WebException) + { + } + return null; + } + + /// + /// Populates the window list with the current running windows. + /// + private void LoadData() + { + _windowList = new ObservableCollection( new WindowFinder().GetWindows().Select( window => new AppWindowViewModel( window ) ) ); + + foreach (var window in _windowList) + { + window.FormattedTitle = new XamlHighlighter().Highlight(new[] { new StringPart(window.AppWindow.Title) }); + window.FormattedProcessTitle = new XamlHighlighter().Highlight(new[] { new StringPart(window.AppWindow.ProcessTitle) }); + } + + lb.DataContext = null; + lb.DataContext = _windowList; + lb.SelectedIndex = 0; + tb.Clear(); + tb.Focus(); + Resize(); + } + + /// + /// Resizes window to match width and height of list. + /// + private void Resize() + { + // These two lines size upon load, but don't whiplash resize during typing + SizeToContent = SizeToContent.WidthAndHeight; + SizeToContent = SizeToContent.Manual; + Left = (SystemParameters.PrimaryScreenWidth / 2) - (ActualWidth / 2); + Top = (SystemParameters.PrimaryScreenHeight / 2) - (ActualHeight / 2); + } + + /// + /// Switches the window associated with the selected item. + /// + private void Switch() + { + if (lb.Items.Count > 0) + { + var win = (AppWindow) (lb.SelectedItem ?? lb.Items[0]); + win.SwitchTo(); + } + HideWindow(); + } + + private void HideWindow() + { + Opacity = 0; + + // Avoid flicker by delaying the "Hide" a bit. This makes sure + // that "Opacity = 0" is taking effect before the window is hidden. + var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) }; + timer.Tick += (sender, args) => + { + Hide(); + timer.Stop(); + }; + timer.Start(); + } + + #endregion + + + /// ================================= + #region Right-click menu functions + /// ================================= + + /// + /// Show Options dialog. + /// + private void Options() + { + if(_optionsWindow == null) + { + _optionsWindow = new OptionsWindow + { + WindowStartupLocation = WindowStartupLocation.CenterScreen + }; + _optionsWindow.Closed += (sender, args) => _optionsWindow = null; + _optionsWindow.ShowDialog(); + } + else + { + _optionsWindow.Activate(); + } + } + + /// + /// Show About dialog. + /// + private void About() + { + if (_aboutWindow == null) + { + _aboutWindow = new AboutWindow + { + WindowStartupLocation = WindowStartupLocation.CenterScreen + }; + _aboutWindow.Closed += (sender, args) => _aboutWindow = null; + _aboutWindow.ShowDialog(); + + } + else + { + _aboutWindow.Activate(); + } + } + + /// + /// Quit Switcheroo + /// + private void Quit() + { + _notifyIcon.Dispose(); + _notifyIcon = null; + _hotkey.Dispose(); + Application.Current.Shutdown(); + } + + #endregion + + + /// ================================= + #region Event Handlers + /// ================================= + + private void OnClose(object sender, System.ComponentModel.CancelEventArgs e) + { + e.Cancel = true; + HideWindow(); + } + + private void hotkey_HotkeyPressed(object sender, EventArgs e) + { + if (Visibility != Visibility.Visible) + { + Show(); + Activate(); + Keyboard.Focus(tb); + LoadData(); + Opacity = 1; + } + else + { + HideWindow(); + } + } + + private void AltTabPressed(object sender, AltTabHookEventArgs e) + { + if (!Settings.Default.AltTabHook) + { + // Ignore Alt+Tab presses if the hook is not activated by the user + return; + } + + e.Handled = true; + + if (Visibility != Visibility.Visible) + { + ActivateAndFocusMainWindow(); + + Keyboard.Focus(tb); + LoadData(); + + if (e.ShiftDown) + { + lb.SelectedIndex = lb.Items.Count - 1; + } + else + { + lb.SelectedIndex = 1; + } + + Opacity = 1; + + } + else + { + if (e.ShiftDown) + { + PreviousItem(); + } + else + { + NextItem(); + } + } + } + + private void ActivateAndFocusMainWindow() + { + // What happens below looks a bit weird, but for Switcheroo to get focus when using the Alt+Tab hook, + // it is needed to simulate an Alt keypress will bring Switcheroo to the foreground. Otherwise Switcheroo + // will become the foreground window, but the previous window will retain focus, and receive keep getting + // the keyboard input. + // http://www.codeproject.com/Tips/76427/How-to-bring-window-to-top-with-SetForegroundWindo + + var thisWindowHandle = new WindowInteropHelper(this).Handle; + var thisWindow = new AppWindow(thisWindowHandle); + + var altKey = new KeyboardKey(Keys.Alt); + var altKeyPressed = false; + + // Press the Alt key if it is not already being pressed + if ((altKey.AsyncState & 0x8000) == 0) + { + altKey.Press(); + altKeyPressed = true; + } + + // Bring the Switcheroo window to the foreground + Show(); + SystemWindow.ForegroundWindow = thisWindow; + Activate(); + + // Release the Alt key if it was pressed above + if (altKeyPressed) + { + altKey.Release(); + } + } + + private void TextChanged(object sender, TextChangedEventArgs args) + { + var text = tb.Text; + + var filterResults = new WindowFilterer().Filter( _windowList.Select( vm => vm.AppWindow ), text ).ToList(); + + foreach (var filterResult in filterResults) + { + filterResult.AppWindow.FormattedTitle = GetFormattedTitleFromBestResult(filterResult.WindowTitleMatchResults); + filterResult.AppWindow.FormattedProcessTitle = GetFormattedTitleFromBestResult(filterResult.ProcessTitleMatchResults); + } + + lb.DataContext = new ObservableCollection( filterResults.Select( r => new AppWindowViewModel( r.AppWindow ) ) ); + if (lb.Items.Count > 0) + { + lb.SelectedItem = lb.Items[0]; + } + } + + private static string GetFormattedTitleFromBestResult(IList matchResults) + { + var bestResult = matchResults.FirstOrDefault(r => r.Matched) ?? matchResults.First(); + return new XamlHighlighter().Highlight(bestResult.StringParts); + } + + private void Hide(object sender, ExecutedRoutedEventArgs e) + { + HideWindow(); + } + + private void OnEnterPressed(object sender, ExecutedRoutedEventArgs e) + { + Switch(); + e.Handled = true; + } + + private void ListBoxItem_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + Switch(); + e.Handled = true; + } + + private void CloseWindow(object sender, ExecutedRoutedEventArgs e) + { + if (lb.Items.Count > 0) + { + var win = (AppWindowViewModel)lb.SelectedItem; + if (win != null) + { + win.AppWindow.PostClose(); + } + } + else + { + HideWindow(); + } + e.Handled = true; + } + + private void ScrollListUp(object sender, ExecutedRoutedEventArgs e) + { + PreviousItem(); + e.Handled = true; + } + + private void PreviousItem() + { + if (lb.Items.Count > 0) + { + if (lb.SelectedIndex != 0) + { + lb.SelectedIndex--; + } + else + { + lb.SelectedIndex = lb.Items.Count - 1; + } + } + } + + private void ScrollListDown(object sender, ExecutedRoutedEventArgs e) + { + NextItem(); + e.Handled = true; + } + + private void NextItem() + { + if (lb.Items.Count > 0) + { + if (lb.SelectedIndex != lb.Items.Count - 1) + { + lb.SelectedIndex++; + } + else + { + lb.SelectedIndex = 0; + } + } + } + + private void MainWindow_OnLostFocus(object sender, EventArgs e) + { + HideWindow(); + } + + #endregion + } +} diff --git a/Switcheroo/Switcheroo.csproj b/Switcheroo/Switcheroo.csproj index 7649fa02..92246a58 100644 --- a/Switcheroo/Switcheroo.csproj +++ b/Switcheroo/Switcheroo.csproj @@ -1,221 +1,231 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {B28E183B-0E38-48A8-8A4E-B4AB7B23CE93} - WinExe - Properties - Switcheroo - switcheroo - v4.5 - 512 - {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 4 - false - icon.ico - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - ..\ - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - false - - - Switcheroo.Program - - - - - - - - 3.5 - - - - - - - 3.5 - - - - - 3.0 - - - 3.0 - - - - - MSBuild:Compile - Designer - MSBuild:Compile - Designer - - - - - - Designer - MSBuild:Compile - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - MSBuild:Compile - Designer - - - - App.xaml - Code - - - - - MainWindow.xaml - Code - - - - - AboutWindow.xaml - - - Component - - - OptionsWindow.xaml - - - - - Code - - - True - True - Resources.resx - - - True - Settings.settings - True - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - false - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - true - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - {b846514d-25be-4274-972a-0e6e74dd0f8b} - Core - - - {fbd3ec1e-47e2-4d2d-81c9-d6506125a09a} - ManagedWinapi - - - - - - - + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {B28E183B-0E38-48A8-8A4E-B4AB7B23CE93} + WinExe + Properties + Switcheroo + switcheroo + v4.5 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + false + icon.ico + + + + + 3.5 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + ..\ + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + false + + + Switcheroo.Program + + + + ..\packages\Caliburn.Micro.Core.2.0.1\lib\net45\Caliburn.Micro.dll + + + ..\packages\Caliburn.Micro.2.0.1\lib\net45\Caliburn.Micro.Platform.dll + + + + + + + 3.5 + + + + + + ..\packages\Caliburn.Micro.2.0.1\lib\net45\System.Windows.Interactivity.dll + + + + 3.5 + + + + + 3.0 + + + 3.0 + + + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + + + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile + Designer + + + + App.xaml + Code + + + + + MainWindow.xaml + Code + + + + + AboutWindow.xaml + + + Component + + + OptionsWindow.xaml + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + false + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + true + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {b846514d-25be-4274-972a-0e6e74dd0f8b} + Core + + + {fbd3ec1e-47e2-4d2d-81c9-d6506125a09a} + ManagedWinapi + + + + + + + + --> \ No newline at end of file diff --git a/Switcheroo/packages.config b/Switcheroo/packages.config index c3298b81..50f1d486 100644 --- a/Switcheroo/packages.config +++ b/Switcheroo/packages.config @@ -1,4 +1,6 @@ - - - + + + + + \ No newline at end of file From 34c7907305892e84bdcd4654a9e6b005300b2eae Mon Sep 17 00:00:00 2001 From: HellBrick Date: Sat, 17 Jan 2015 15:42:30 +0300 Subject: [PATCH 05/18] The window is marked on closing --- Switcheroo/AppWindowViewModel.cs | 7 +++++ Switcheroo/MainWindow.xaml | 47 +++++++++++++++++++++++++++++--- Switcheroo/MainWindow.xaml.cs | 1 + 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/Switcheroo/AppWindowViewModel.cs b/Switcheroo/AppWindowViewModel.cs index 742f328f..aab56bee 100644 --- a/Switcheroo/AppWindowViewModel.cs +++ b/Switcheroo/AppWindowViewModel.cs @@ -36,6 +36,13 @@ public string FormattedProcessTitle set { AppWindow.FormattedProcessTitle = value; NotifyOfPropertyChange( () => FormattedProcessTitle ); } } + private bool _isBeingClosed = false; + public bool IsBeingClosed + { + get { return _isBeingClosed; } + set { _isBeingClosed = value; NotifyOfPropertyChange( () => IsBeingClosed ); } + } + #endregion } } \ No newline at end of file diff --git a/Switcheroo/MainWindow.xaml b/Switcheroo/MainWindow.xaml index a040055f..32112bb4 100644 --- a/Switcheroo/MainWindow.xaml +++ b/Switcheroo/MainWindow.xaml @@ -47,6 +47,37 @@ + + 00:00:00.150 + + + @@ -64,10 +95,18 @@ - - + + + + + + + + + + diff --git a/Switcheroo/MainWindow.xaml.cs b/Switcheroo/MainWindow.xaml.cs index e84a7ddc..6e536b79 100644 --- a/Switcheroo/MainWindow.xaml.cs +++ b/Switcheroo/MainWindow.xaml.cs @@ -465,6 +465,7 @@ private void CloseWindow(object sender, ExecutedRoutedEventArgs e) if (win != null) { win.AppWindow.PostClose(); + win.IsBeingClosed = true; } } else From c4d0dac1e5734bf5e8c3fc05d4f16552c9b17f54 Mon Sep 17 00:00:00 2001 From: HellBrick Date: Sat, 17 Jan 2015 16:53:03 +0300 Subject: [PATCH 06/18] Migrated WindowFilterer to AppWindowViewModel to preserve IsBeingClosed state --- {Switcheroo => Core}/AppWindowViewModel.cs | 3 +- Core/Core.csproj | 157 +++++++++++---------- Core/FilterResult.cs | 2 +- Core/WindowFilterer.cs | 4 +- Core/packages.config | 5 + Switcheroo/MainWindow.xaml.cs | 4 +- Switcheroo/Switcheroo.csproj | 1 - 7 files changed, 96 insertions(+), 80 deletions(-) rename {Switcheroo => Core}/AppWindowViewModel.cs (91%) create mode 100644 Core/packages.config diff --git a/Switcheroo/AppWindowViewModel.cs b/Core/AppWindowViewModel.cs similarity index 91% rename from Switcheroo/AppWindowViewModel.cs rename to Core/AppWindowViewModel.cs index aab56bee..85564961 100644 --- a/Switcheroo/AppWindowViewModel.cs +++ b/Core/AppWindowViewModel.cs @@ -4,9 +4,8 @@ using System.Text; using System.Threading.Tasks; using Caliburn.Micro; -using Switcheroo.Core; -namespace Switcheroo +namespace Switcheroo.Core { public class AppWindowViewModel: PropertyChangedBase { diff --git a/Core/Core.csproj b/Core/Core.csproj index c5b82740..bc3893de 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -1,79 +1,92 @@ - - - - - Debug - AnyCPU - {B846514D-25BE-4274-972A-0E6E74DD0F8B} - Library - Properties - Switcheroo.Core - Switcheroo.Core - v4.5 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {fbd3ec1e-47e2-4d2d-81c9-d6506125a09a} - ManagedWinapi - - - + + + + + Debug + AnyCPU + {B846514D-25BE-4274-972A-0E6E74DD0F8B} + Library + Properties + Switcheroo.Core + Switcheroo.Core + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Caliburn.Micro.Core.2.0.1\lib\net45\Caliburn.Micro.dll + + + ..\packages\Caliburn.Micro.2.0.1\lib\net45\Caliburn.Micro.Platform.dll + + + + + + + + + ..\packages\Caliburn.Micro.2.0.1\lib\net45\System.Windows.Interactivity.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {fbd3ec1e-47e2-4d2d-81c9-d6506125a09a} + ManagedWinapi + + + + + + + --> \ No newline at end of file diff --git a/Core/FilterResult.cs b/Core/FilterResult.cs index 0b1f7316..0471026e 100644 --- a/Core/FilterResult.cs +++ b/Core/FilterResult.cs @@ -25,7 +25,7 @@ namespace Switcheroo.Core { public class FilterResult { - public AppWindow AppWindow { get; set; } + public AppWindowViewModel AppWindow { get; set; } public IList WindowTitleMatchResults { get; set; } public IList ProcessTitleMatchResults { get; set; } } diff --git a/Core/WindowFilterer.cs b/Core/WindowFilterer.cs index c2947f96..2fe066d3 100644 --- a/Core/WindowFilterer.cs +++ b/Core/WindowFilterer.cs @@ -26,10 +26,10 @@ namespace Switcheroo.Core { public class WindowFilterer { - public IEnumerable Filter(IEnumerable windows, string filterText) + public IEnumerable Filter(IEnumerable windows, string filterText) { return windows - .Select(w => new { Window = w, ResultsTitle = Score(w.Title, filterText), ResultsProcessTitle = Score(w.ProcessTitle, filterText) }) + .Select(w => new { Window = w, ResultsTitle = Score(w.AppWindow.Title, filterText), ResultsProcessTitle = Score(w.AppWindow.ProcessTitle, filterText) }) .Where(r => r.ResultsTitle.Any(wt => wt.Matched) || r.ResultsProcessTitle.Any(pt => pt.Matched)) .OrderByDescending(r => r.ResultsTitle.Sum(wt => wt.Score) + r.ResultsProcessTitle.Sum(pt => pt.Score)) .Select(r => new FilterResult { AppWindow = r.Window, WindowTitleMatchResults = r.ResultsTitle, ProcessTitleMatchResults = r.ResultsProcessTitle }); diff --git a/Core/packages.config b/Core/packages.config new file mode 100644 index 00000000..d9c8ba93 --- /dev/null +++ b/Core/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Switcheroo/MainWindow.xaml.cs b/Switcheroo/MainWindow.xaml.cs index 6e536b79..4a5cb646 100644 --- a/Switcheroo/MainWindow.xaml.cs +++ b/Switcheroo/MainWindow.xaml.cs @@ -419,7 +419,7 @@ private void TextChanged(object sender, TextChangedEventArgs args) { var text = tb.Text; - var filterResults = new WindowFilterer().Filter( _windowList.Select( vm => vm.AppWindow ), text ).ToList(); + var filterResults = new WindowFilterer().Filter( _windowList, text ).ToList(); foreach (var filterResult in filterResults) { @@ -427,7 +427,7 @@ private void TextChanged(object sender, TextChangedEventArgs args) filterResult.AppWindow.FormattedProcessTitle = GetFormattedTitleFromBestResult(filterResult.ProcessTitleMatchResults); } - lb.DataContext = new ObservableCollection( filterResults.Select( r => new AppWindowViewModel( r.AppWindow ) ) ); + lb.DataContext = new ObservableCollection( filterResults.Select( r => r.AppWindow ) ); if (lb.Items.Count > 0) { lb.SelectedItem = lb.Items[0]; diff --git a/Switcheroo/Switcheroo.csproj b/Switcheroo/Switcheroo.csproj index 92246a58..2b7acbde 100644 --- a/Switcheroo/Switcheroo.csproj +++ b/Switcheroo/Switcheroo.csproj @@ -102,7 +102,6 @@ MSBuild:Compile Designer - From 661e06beee2ff82b53d35f48769e4d25e237c4c4 Mon Sep 17 00:00:00 2001 From: HellBrick Date: Sat, 17 Jan 2015 18:34:59 +0300 Subject: [PATCH 07/18] Fixed animation glitch --- Switcheroo/BoolToWhateverConverter.cs | 39 +++++++++++++++++++++++++++ Switcheroo/MainWindow.xaml | 22 ++++++++++----- Switcheroo/Switcheroo.csproj | 1 + 3 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 Switcheroo/BoolToWhateverConverter.cs diff --git a/Switcheroo/BoolToWhateverConverter.cs b/Switcheroo/BoolToWhateverConverter.cs new file mode 100644 index 00000000..da3790fb --- /dev/null +++ b/Switcheroo/BoolToWhateverConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; +using System.Windows.Media; + +namespace Switcheroo +{ + public class BoolConverter: IValueConverter + { + public T IfTrue { get; set; } + public T IfFalse { get; set; } + + #region IValueConverter Members + + public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) + { + return ( (bool) value ) ? IfTrue : IfFalse; + } + + public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) + { + throw new NotImplementedException(); + } + + #endregion + } + + + public class BoolToDoubleConverter: BoolConverter + { + } + + public class BoolToColorConverter: BoolConverter + { + } +} diff --git a/Switcheroo/MainWindow.xaml b/Switcheroo/MainWindow.xaml index 32112bb4..601495ad 100644 --- a/Switcheroo/MainWindow.xaml +++ b/Switcheroo/MainWindow.xaml @@ -2,6 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Switcheroo" + xmlns:system="clr-namespace:System;assembly=mscorlib" Closing="OnClose" ResizeMode="NoResize" ShowInTaskbar="False" WindowStyle="None" AllowsTransparency="True" Title="" Height="150" Width="400" Deactivated="MainWindow_OnLostFocus"> @@ -49,6 +50,14 @@ 00:00:00.150 + + 0.5 + + + Silver + + +