Skip to content

Commit

Permalink
Maybe fix memory leak caused by not disposing Icons and Bitmaps.
Browse files Browse the repository at this point in the history
Icon implements IDisposable and must therefore be disposed.
AudioDeviceIconExtractor creates and caches a lot of icons but never
frees old cache entries. TrayIcon.UpdateIcon also creates many copies of
Resources.SoundSwitch16. Getters in the generated Resource class always
return new instances of Bitmaps and Icons.
This commit removes the caching and adds code to manually dispose icons
used in various locations. Some uses of the Resource class are cached
instead (where the resource is not disposed anyways) to avoid loading
the same resource multiple times from disk. I didn't touch generated
files, though.

This commit is untested since I cannot compile the project.
  • Loading branch information
Niko-O committed May 11, 2018
1 parent 3e8d7c6 commit 4a7504d
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ public class NotificationBanner : INotification

public void NotifyDefaultChanged(MMDevice audioDevice)
{
var Icon = AudioDeviceIconExtractor.ExtractIconFromAudioDevice(audioDevice, true);
var toastData = new BannerData
{
Image = AudioDeviceIconExtractor.ExtractIconFromAudioDevice(audioDevice, true).ToBitmap(),
Image = Icon.ToBitmap(),
Text = audioDevice.FriendlyName
};
Icon.Dispose();
if (Configuration.CustomSound != null && File.Exists(Configuration.CustomSound.FilePath))
{
toastData.SoundFile = Configuration.CustomSound;
Expand Down
3 changes: 2 additions & 1 deletion SoundSwitch/UI/Forms/About.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ namespace SoundSwitch.UI.Forms
{
public partial class About : Form
{
private static readonly System.Drawing.Icon helpIcon = Resources.HelpIcon;

public About()
{
InitializeComponent();
Icon = Resources.HelpIcon;
Icon = helpIcon;
}

private void About_Load(object sender, System.EventArgs e)
Expand Down
23 changes: 22 additions & 1 deletion SoundSwitch/UI/Forms/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ namespace SoundSwitch.UI.Forms
{
public sealed partial class SettingsForm : Form
{
private static readonly Icon settingsIcon = Resources.SettingsIcon;

private readonly bool _loaded;

public SettingsForm()
{
// Form itself
InitializeComponent();
Icon = Resources.SettingsIcon;
Icon = settingsIcon;
Text = AssemblyUtils.GetReleaseState() == AssemblyUtils.ReleaseState.Beta ?
$"{SettingsStrings.settings} {AssemblyUtils.GetReleaseState()}" :
SettingsStrings.settings;
Expand Down Expand Up @@ -234,6 +236,25 @@ private void closeButton_Click(object sender, EventArgs e)
Close();
}

protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
foreach (var i in playbackListView.SmallImageList.Images)
{
if (i is IDisposable)
{
((IDisposable)i).Dispose();
}
}
foreach (var i in recordingListView.SmallImageList.Images)
{
if (i is IDisposable)
{
((IDisposable)i).Dispose();
}
}
}

private void tabControl_SelectedIndexChanged(object sender, EventArgs e)
{
var tabControlSender = (TabControl)sender;
Expand Down
4 changes: 3 additions & 1 deletion SoundSwitch/UI/Forms/UpdateDownloadForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ namespace SoundSwitch.UI.Forms
{
public sealed partial class UpdateDownloadForm : Form
{
private static readonly System.Drawing.Icon updateIcon = Resources.UpdateIcon;

private readonly bool _redirectLinks = false;
private readonly WebFile _releaseFile;

public UpdateDownloadForm(Release release)
{
InitializeComponent();
Icon = Resources.UpdateIcon;
Icon = updateIcon;
Text = release.Name;
LocalizeForm();
Focus();
Expand Down
66 changes: 6 additions & 60 deletions SoundSwitch/Util/AudioDeviceIconExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,51 +24,8 @@ namespace SoundSwitch.Util
{
internal class AudioDeviceIconExtractor
{
private class IconKey : IEquatable<IconKey>
{
private string FilePath { get; }
private bool Large { get; }

public IconKey(string filePath, bool large)
{
FilePath = filePath;
Large = large;
}

public override int GetHashCode()
{
unchecked
{
return (FilePath.GetHashCode()*397) ^ Large.GetHashCode();
}
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((IconKey) obj);
}

public static bool operator ==(IconKey left, IconKey right)
{
return Equals(left, right);
}

public static bool operator !=(IconKey left, IconKey right)
{
return !Equals(left, right);
}

public bool Equals(IconKey other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(FilePath, other.FilePath) && Large == other.Large;
}
}
private static readonly Dictionary<IconKey, Icon> IconCache = new Dictionary<IconKey, Icon>();
private static readonly Icon defaultSpeakers = Resources.defaultSpeakers;
private static readonly Icon defaultMicrophone = Resources.defaultMicrophone;

/// <summary>
/// Extract the Icon out of an AudioDevice
Expand All @@ -78,24 +35,18 @@ public bool Equals(IconKey other)
/// <returns></returns>
public static Icon ExtractIconFromAudioDevice(MMDevice audioDevice, bool largeIcon)
{
Icon ico;
var iconKey = new IconKey(audioDevice.IconPath, largeIcon);
if (IconCache.TryGetValue(iconKey, out ico))
{
return ico;
}
try
{
if (audioDevice.IconPath.EndsWith(".ico"))
{
ico = Icon.ExtractAssociatedIcon(audioDevice.IconPath);
return Icon.ExtractAssociatedIcon(audioDevice.IconPath);
}
else
{
var iconInfo = audioDevice.IconPath.Split(',');
var dllPath = iconInfo[0];
var iconIndex = int.Parse(iconInfo[1]);
ico = IconExtractor.Extract(dllPath, iconIndex, largeIcon);
return IconExtractor.Extract(dllPath, iconIndex, largeIcon);
}
}
catch (Exception e)
Expand All @@ -104,18 +55,13 @@ public static Icon ExtractIconFromAudioDevice(MMDevice audioDevice, bool largeIc
switch (audioDevice.DataFlow)
{
case DataFlow.Capture:
ico = Resources.defaultSpeakers;
break;
return defaultSpeakers;
case DataFlow.Render:
ico = Resources.defaultMicrophone;
break;
return defaultMicrophone;
default:
throw new ArgumentOutOfRangeException();
}
}

IconCache.Add(iconKey, ico);
return ico;
}
}
}
4 changes: 3 additions & 1 deletion SoundSwitch/Util/ToolStripDeviceItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ namespace SoundSwitch.Util
{
internal class ToolStripDeviceItem : ToolStripMenuItem
{
private static readonly Bitmap check = Resources.Check;

public ToolStripDeviceItem(EventHandler onClick, MMDevice audioDevice)
: base(audioDevice.FriendlyName, null, onClick)
{
Expand All @@ -35,7 +37,7 @@ public override Image Image
get
{
if (AudioDevice !=null && AudioController.IsDefault(AudioDevice.ID, (DeviceType) AudioDevice.DataFlow, DeviceRole.Console))
return Resources.Check;
return check;

return null;
}
Expand Down
57 changes: 41 additions & 16 deletions SoundSwitch/Util/TrayIcon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ namespace SoundSwitch.Util
{
public sealed class TrayIcon : IDisposable
{
private static readonly Bitmap updateBitmap = Resources.Update;
private static readonly Bitmap settingsSmall = Resources.SettingsSmall;
private static readonly Bitmap soundSwitch16 = Resources.SoundSwitch16;
private static readonly Bitmap playbackDevices = Resources.PlaybackDevices;
private static readonly Bitmap mixer = Resources.Mixer;
private static readonly Bitmap infoHelp = Resources.InfoHelp;
private static readonly Bitmap donate = Resources.donate;
private static readonly Bitmap helpSmall = Resources.HelpSmall;
private static readonly Bitmap exit = Resources.exit;
private static readonly Icon updateIcon = Resources.UpdateIcon;

private readonly ContextMenuStrip _selectionMenu = new ContextMenuStrip();
private readonly ContextMenuStrip _settingsMenu = new ContextMenuStrip();
private readonly SynchronizationContext _context = SynchronizationContext.Current ?? new SynchronizationContext();
Expand All @@ -55,15 +66,15 @@ public TrayIcon()
{
UpdateIcon();
_tooltipInfoManager = new TooltipInfoManager(NotifyIcon);
_updateMenuItem = new ToolStripMenuItem(TrayIconStrings.noUpdate, Resources.Update, OnUpdateClick)
_updateMenuItem = new ToolStripMenuItem(TrayIconStrings.noUpdate, updateBitmap, OnUpdateClick)
{
Enabled = false
};
NotifyIcon.ContextMenuStrip = _settingsMenu;

PopulateSettingsMenu();

_selectionMenu.Items.Add(TrayIconStrings.noDevicesSelected, Resources.SettingsSmall, (sender, e) => ShowSettings());
_selectionMenu.Items.Add(TrayIconStrings.noDevicesSelected, settingsSmall, (sender, e) => ShowSettings());

NotifyIcon.MouseDoubleClick += (sender, args) =>
{
Expand Down Expand Up @@ -104,23 +115,37 @@ public void Dispose()
{
_selectionMenu.Dispose();
_settingsMenu.Dispose();
if (NotifyIcon.Icon != null)
{
NotifyIcon.Icon.Dispose();
}
NotifyIcon.Dispose();
_updateMenuItem.Dispose();
}

private void ReplaceIcon(Icon newIcon)
{
var oldIcon = NotifyIcon.Icon;
NotifyIcon.Icon = newIcon;
if (oldIcon != null)
{
oldIcon.Dispose();
}
}

public void UpdateIcon()
{
if (AppConfigs.Configuration.KeepSystrayIcon)
{
NotifyIcon.Icon = Icon.FromHandle(Resources.SoundSwitch16.GetHicon());
ReplaceIcon(Icon.FromHandle(soundSwitch16.GetHicon()));
return;
}

try
{
var defaultDevice = AppModel.Instance.ActiveAudioDeviceLister.GetPlaybackDevices()
.First(device => AudioController.IsDefault(device.ID, (DeviceType)device.DataFlow, DeviceRole.Console));
NotifyIcon.Icon = AudioDeviceIconExtractor.ExtractIconFromAudioDevice(defaultDevice, false);
ReplaceIcon(AudioDeviceIconExtractor.ExtractIconFromAudioDevice(defaultDevice, false));
}
catch (InvalidOperationException)
{
Expand All @@ -134,17 +159,17 @@ private void PopulateSettingsMenu()
var readmeHtml = Path.Combine(applicationDirectory, "Readme.html");
_settingsMenu.Items.Add(
Application.ProductName + ' ' + AssemblyUtils.GetReleaseState() + " (" + Application.ProductVersion +
")", Resources.SoundSwitch16);
")", soundSwitch16);
_settingsMenu.Items.Add("-");
_settingsMenu.Items.Add(TrayIconStrings.playbackDevices, Resources.PlaybackDevices,
_settingsMenu.Items.Add(TrayIconStrings.playbackDevices, playbackDevices,
(sender, e) => { Process.Start(new ProcessStartInfo("control", "mmsys.cpl sounds")); });
_settingsMenu.Items.Add(TrayIconStrings.mixer, Resources.Mixer,
_settingsMenu.Items.Add(TrayIconStrings.mixer, mixer,
(sender, e) => { Process.Start(new ProcessStartInfo("sndvol.exe")); });
_settingsMenu.Items.Add("-");
_settingsMenu.Items.Add(_updateMenuItem);
_settingsMenu.Items.Add(TrayIconStrings.settings, Resources.SettingsSmall, (sender, e) => ShowSettings());
_settingsMenu.Items.Add(TrayIconStrings.settings, settingsSmall, (sender, e) => ShowSettings());
_settingsMenu.Items.Add("-");
_settingsMenu.Items.Add(TrayIconStrings.help, Resources.InfoHelp, (sender, e) =>
_settingsMenu.Items.Add(TrayIconStrings.help, infoHelp, (sender, e) =>
{
if (!File.Exists(readmeHtml))
{
Expand All @@ -153,10 +178,10 @@ private void PopulateSettingsMenu()
}
Process.Start(readmeHtml);
});
_settingsMenu.Items.Add(TrayIconStrings.donate, Resources.donate, (sender, e) => Process.Start("https://soundswitch.aaflalo.me/?utm_source=application"));
_settingsMenu.Items.Add(TrayIconStrings.about, Resources.HelpSmall, (sender, e) => new About().Show());
_settingsMenu.Items.Add(TrayIconStrings.donate, donate, (sender, e) => Process.Start("https://soundswitch.aaflalo.me/?utm_source=application"));
_settingsMenu.Items.Add(TrayIconStrings.about, helpSmall, (sender, e) => new About().Show());
_settingsMenu.Items.Add("-");
_settingsMenu.Items.Add(TrayIconStrings.exit, Resources.exit, (sender, e) => Application.Exit());
_settingsMenu.Items.Add(TrayIconStrings.exit, exit, (sender, e) => Application.Exit());
}

private void OnUpdateClick(object sender, EventArgs eventArgs)
Expand Down Expand Up @@ -190,7 +215,7 @@ private void SetEventHandlers()
{
return;
}
NotifyIcon.Icon = AudioDeviceIconExtractor.ExtractIconFromAudioDevice(audioChangeEvent.Device, false);
ReplaceIcon(AudioDeviceIconExtractor.ExtractIconFromAudioDevice(audioChangeEvent.Device, false));
};
AppModel.Instance.NewVersionReleased += (sender, @event) =>
{
Expand Down Expand Up @@ -224,9 +249,9 @@ private void StartAnimationIconUpdate()
var tick = 0;
_animationTimer.Tick += (sender, args) =>
{
NotifyIcon.Icon = tick == 0
? Icon.FromHandle(Resources.SoundSwitch16.GetHicon())
: Resources.UpdateIcon;
ReplaceIcon(tick == 0
? Icon.FromHandle(soundSwitch16.GetHicon())
: (Icon)updateIcon.Clone());
tick = ++tick % 2;
};
}
Expand Down

0 comments on commit 4a7504d

Please sign in to comment.