Skip to content

Commit

Permalink
Use icons from the taskbar #19
Browse files Browse the repository at this point in the history
Now the icon of a window is used instead of the icon of the
process that owns the window.

Icons are being loaded async now, so Switcheroo should show up
faster as well.
  • Loading branch information
kvakulo committed Oct 28, 2014
1 parent 3d9e056 commit e5de075
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 79 deletions.
107 changes: 33 additions & 74 deletions Core/AppWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,21 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.Caching;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Media.Imaging;
using ManagedWinapi.Windows;
using Microsoft.Win32;

namespace Switcheroo.Core
{

/// <summary>
/// This class is a wrapper around the Win32 api window handles
/// </summary>
public class AppWindow : ManagedWinapi.Windows.SystemWindow
public class AppWindow : SystemWindow
{
public string FormattedTitle { get; set; }

Expand All @@ -59,82 +56,21 @@ public string ProcessTitle

public string FormattedProcessTitle { get; set; }

public BitmapImage IconImage
{
get
{
var key = "IconImage-" + HWnd;
var iconImage = MemoryCache.Default.Get(key) as BitmapImage;
if (iconImage == null)
{
iconImage = ExtractIcon() ?? new BitmapImage();
MemoryCache.Default.Add(key, iconImage, DateTimeOffset.Now.AddHours(1));
}
return iconImage;
}
}

private BitmapImage ExtractIcon()
{
Icon extractAssociatedIcon = null;
try
{
extractAssociatedIcon = Icon.ExtractAssociatedIcon(GetExecutablePath(Process));
}
catch (Win32Exception)
{
// Could not extract icon
}
if (extractAssociatedIcon == null)
{
return null;
}

using (var memory = new MemoryStream())
{
var bitmap = extractAssociatedIcon.ToBitmap();
bitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
public Icon LargeWindowIcon
{
get { return new WindowIconFinder().Find(this, WindowIconSize.Large); }
}

private static string GetExecutablePath(Process process)
public Icon SmallWindowIcon
{
// If Vista or later
if (Environment.OSVersion.Version.Major >= 6)
{
return GetExecutablePathAboveVista(process.Id);
}

return process.MainModule.FileName;
get { return new WindowIconFinder().Find(this, WindowIconSize.Small); }
}

private static string GetExecutablePathAboveVista(int processId)
public string ExecutablePath
{
var buffer = new StringBuilder(1024);
var hprocess = WinApi.OpenProcess(WinApi.ProcessAccess.QueryLimitedInformation, false, processId);
if (hprocess == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());

try
{
// ReSharper disable once RedundantAssignment
var size = buffer.Capacity;
if (WinApi.QueryFullProcessImageName(hprocess, 0, buffer, out size))
{
return buffer.ToString();
}
}
finally
{
WinApi.CloseHandle(hprocess);
}
throw new Win32Exception(Marshal.GetLastWin32Error());
get { return GetExecutablePath(Process.Id); }
}

public AppWindow(IntPtr HWnd) : base(HWnd) { }
Expand Down Expand Up @@ -182,7 +118,7 @@ public bool IsAltTabWindow()

private bool HasWindowTitle()
{
return Title.Length > 0;
return !string.IsNullOrEmpty(Title);
}

private bool IsToolWindow()
Expand Down Expand Up @@ -223,5 +159,28 @@ private bool IsOwnerOrOwnerNotVisible()
{
return Owner == null || !Owner.Visible;
}

// This method only works on Windows >= Windows Vista
private static string GetExecutablePath(int processId)
{
var buffer = new StringBuilder(1024);
var hprocess = WinApi.OpenProcess(WinApi.ProcessAccess.QueryLimitedInformation, false, processId);
if (hprocess == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());

try
{
// ReSharper disable once RedundantAssignment
var size = buffer.Capacity;
if (WinApi.QueryFullProcessImageName(hprocess, 0, buffer, out size))
{
return buffer.ToString();
}
}
finally
{
WinApi.CloseHandle(hprocess);
}
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
1 change: 1 addition & 0 deletions Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="FilterResult.cs" />
<Compile Include="WinApi.cs" />
<Compile Include="WindowIconFinder.cs" />
<Compile Include="XamlHighlighter.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
54 changes: 54 additions & 0 deletions Core/WinApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,59 @@ public enum MapVirtualKeyMapTypes : uint
/// </summary>
MAPVK_VK_TO_VSC_EX = 0x04
}

[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hwnd, int message, int wParam, IntPtr lParam);

[DllImport("user32.dll")]
public static extern IntPtr DefWindowProc(IntPtr hWnd, int message, int wParam, IntPtr lParam);

public enum ClassLongFlags
{
GCLP_MENUNAME = -8,
GCLP_HBRBACKGROUND = -10,
GCLP_HCURSOR = -12,
GCLP_HICON = -14,
GCLP_HMODULE = -16,
GCL_CBWNDEXTRA = -18,
GCL_CBCLSEXTRA = -20,
GCLP_WNDPROC = -24,
GCL_STYLE = -26,
GCLP_HICONSM = -34,
GCW_ATOM = -32
}

public static IntPtr GetClassLongPtr(IntPtr hWnd, ClassLongFlags flags)
{
return IntPtr.Size > 4 ? GetClassLongPtr64(hWnd, flags) : new IntPtr(GetClassLongPtr32(hWnd, flags));
}

[DllImport("user32.dll", EntryPoint = "GetClassLong")]
public static extern uint GetClassLongPtr32(IntPtr hWnd, ClassLongFlags flags);

[DllImport("user32.dll", EntryPoint = "GetClassLongPtr")]
public static extern IntPtr GetClassLongPtr64(IntPtr hWnd, ClassLongFlags flags);

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern uint RegisterWindowMessage(string lpString);

[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(
IntPtr hWnd,
uint Msg,
IntPtr wParam,
IntPtr lParam,
SendMessageTimeoutFlags fuFlags,
uint uTimeout,
out IntPtr lpdwResult);

[Flags]
public enum SendMessageTimeoutFlags : uint
{
SMTO_NORMAL = 0x0,
SMTO_BLOCK = 0x1,
SMTO_ABORTIFHUNG = 0x2,
SMTO_NOTIMEOUTIFNOTHUNG = 0x8
}
}
}
68 changes: 68 additions & 0 deletions Core/WindowIconFinder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

using System;
using System.ComponentModel;
using System.Drawing;

namespace Switcheroo.Core
{
public enum WindowIconSize
{
Small,
Large
}

public class WindowIconFinder
{
public Icon Find(AppWindow window, WindowIconSize size)
{
Icon icon = null;
try
{
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms632625(v=vs.85).aspx
IntPtr response;
var outvalue = WinApi.SendMessageTimeout(window.HWnd, 0x007F, size == WindowIconSize.Small ? new IntPtr(2) : new IntPtr(1),
IntPtr.Zero, WinApi.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG, 100, out response);

if (outvalue == IntPtr.Zero || response == IntPtr.Zero)
{
response = WinApi.GetClassLongPtr(window.HWnd,
size == WindowIconSize.Small ? WinApi.ClassLongFlags.GCLP_HICONSM : WinApi.ClassLongFlags.GCLP_HICON);
}

if (response != IntPtr.Zero)
{
icon = Icon.FromHandle(response);
}
else
{
var executablePath = window.ExecutablePath;
icon = Icon.ExtractAssociatedIcon(executablePath);
}
}
catch (Win32Exception)
{
// Could not extract icon
}
return icon;
}
}
}
2 changes: 1 addition & 1 deletion Switcheroo.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.30110.0
VisualStudioVersion = 12.0.30723.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Switcheroo", "Switcheroo\Switcheroo.csproj", "{B28E183B-0E38-48A8-8A4E-B4AB7B23CE93}"
ProjectSection(ProjectDependencies) = postProject
Expand Down
51 changes: 51 additions & 0 deletions Switcheroo/IconToBitmapConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows.Media.Imaging;

namespace Switcheroo
{
public class IconToBitmapImageConverter
{
public BitmapImage Convert(Icon icon)
{
if (icon == null)
{
return null;
}

using (var memory = new MemoryStream())
{
var bitmap = icon.ToBitmap();
bitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
}
}
}
20 changes: 16 additions & 4 deletions Switcheroo/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,14 @@
<KeyBinding Command="Close" Key="Escape" />
<KeyBinding Command="local:MainWindow.ScrollListUpCommand" Key="Up" />
<KeyBinding Command="local:MainWindow.ScrollListDownCommand" Key="Down" />
</Window.InputBindings>

<Grid>
</Window.InputBindings>

<Window.Resources>
<local:WindowHandleToIconConverter x:Key="WindowHandleToIconConverter" />
<local:WindowHandleToCachedIconConverter x:Key="WindowHandleToCachedIconConverter" />
</Window.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
Expand All @@ -51,7 +56,14 @@
<ColumnDefinition Width="400" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<Image Source="{Binding Path=IconImage}" Height="19" Grid.Row="0" Grid.Column="0" Margin="3" />
<Image Height="19" Grid.Row="0" Grid.Column="0" Margin="3">
<Image.Source>
<PriorityBinding>
<Binding Path="HWnd" Converter="{StaticResource WindowHandleToIconConverter}" IsAsync="True"/>
<Binding Path="HWnd" Converter="{StaticResource WindowHandleToCachedIconConverter}" />
</PriorityBinding>
</Image.Source>
</Image>
<TextBlock local:FormattedTextAttribute.FormattedText="{Binding Path=FormattedTitle}"
Grid.Row="0" Grid.Column="1" Margin="3" />
<TextBlock local:FormattedTextAttribute.FormattedText="{Binding Path=FormattedProcessTitle}"
Expand Down
3 changes: 3 additions & 0 deletions Switcheroo/Switcheroo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="IconToBitmapConverter.cs" />
<Compile Include="WindowHandleToCachedIconConverter.cs" />
<Compile Include="WindowHandleToIconConverter.cs" />
<Page Include="AboutWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
Loading

0 comments on commit e5de075

Please sign in to comment.