├── LICENSE ├── Toastify ├── spotify.ico ├── SpotifyLogo.png ├── ManagedWinapi.dll ├── Resources │ ├── spotify.ico │ ├── InfoLarge.png │ ├── thumbs_up.png │ ├── WarningLarge.png │ ├── WarningSmall.png │ ├── thumbs_down.png │ └── ManagedWinapiNativeHelper.dll ├── SpotifyToastifyLogo.png ├── ToastifyAccessDenied.png ├── SpotifyToastifyUpdateLogo.png ├── app.config ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Toastify.csproj.vspscc ├── packages.config ├── App.xaml ├── Spotify │ ├── SpotifyAction.cs │ ├── Song.cs │ ├── SpotifyApiClient.cs │ └── Spotify.cs ├── Toastify.csproj.user ├── BoolToVisibleConverter.cs ├── OppositeBoolConverter.cs ├── OppositeBoolToVisibleConverter.cs ├── App.xaml.cs ├── ScreenHelper.cs ├── LastInputDebug.cs ├── About.xaml.cs ├── VersionChecker.cs ├── WinHelper.cs ├── Toast.xaml ├── About.xaml ├── Telemetry.cs ├── Win32.cs ├── Settings.xaml.cs ├── HotKey.cs ├── Toastify.csproj ├── VolumeHelper.cs └── Settings.xaml ├── InstallationScript ├── KillProcWMI.dll ├── killproc_README.txt ├── Install.nsi └── DotNET.nsh ├── AutoHotkey.Interop ├── x64 │ └── AutoHotkey.dll ├── x86 │ └── AutoHotkey.dll ├── AutoHotkey.Interop.csproj.vspscc ├── SafeLibraryHandle.cs ├── Properties │ └── AssemblyInfo.cs ├── Util.cs ├── AutoHotkey.Interop.csproj ├── AutoHotkeyEngine.cs └── AutoHotkeyDll.cs ├── packages └── repositories.config ├── Toastify.vssscc ├── PluginBase ├── PluginBase.csproj.vspscc ├── PluginBase.cs ├── Properties │ └── AssemblyInfo.cs └── Toastify.Plugin.csproj ├── ExamplePlugin ├── ExamplePlugin.csproj.vspscc ├── Properties │ └── AssemblyInfo.cs ├── ExamplePlugin.cs └── ExamplePlugin.csproj ├── Toastify.sln ├── BuildProcessTemplates └── UpgradeTemplate.xaml └── .gitignore /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/LICENSE -------------------------------------------------------------------------------- /Toastify/spotify.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/spotify.ico -------------------------------------------------------------------------------- /Toastify/SpotifyLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/SpotifyLogo.png -------------------------------------------------------------------------------- /Toastify/ManagedWinapi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/ManagedWinapi.dll -------------------------------------------------------------------------------- /Toastify/Resources/spotify.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/Resources/spotify.ico -------------------------------------------------------------------------------- /Toastify/Resources/InfoLarge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/Resources/InfoLarge.png -------------------------------------------------------------------------------- /Toastify/Resources/thumbs_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/Resources/thumbs_up.png -------------------------------------------------------------------------------- /Toastify/SpotifyToastifyLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/SpotifyToastifyLogo.png -------------------------------------------------------------------------------- /InstallationScript/KillProcWMI.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/InstallationScript/KillProcWMI.dll -------------------------------------------------------------------------------- /Toastify/Resources/WarningLarge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/Resources/WarningLarge.png -------------------------------------------------------------------------------- /Toastify/Resources/WarningSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/Resources/WarningSmall.png -------------------------------------------------------------------------------- /Toastify/Resources/thumbs_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/Resources/thumbs_down.png -------------------------------------------------------------------------------- /Toastify/ToastifyAccessDenied.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/ToastifyAccessDenied.png -------------------------------------------------------------------------------- /AutoHotkey.Interop/x64/AutoHotkey.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/AutoHotkey.Interop/x64/AutoHotkey.dll -------------------------------------------------------------------------------- /AutoHotkey.Interop/x86/AutoHotkey.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/AutoHotkey.Interop/x86/AutoHotkey.dll -------------------------------------------------------------------------------- /Toastify/SpotifyToastifyUpdateLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/SpotifyToastifyUpdateLogo.png -------------------------------------------------------------------------------- /Toastify/Resources/ManagedWinapiNativeHelper.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nachmore/toastify/HEAD/Toastify/Resources/ManagedWinapiNativeHelper.dll -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Toastify/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Toastify/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Toastify.vssscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT" 10 | } 11 | -------------------------------------------------------------------------------- /Toastify/Toastify.csproj.vspscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" 10 | } 11 | -------------------------------------------------------------------------------- /Toastify/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /PluginBase/PluginBase.csproj.vspscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" 10 | } 11 | -------------------------------------------------------------------------------- /ExamplePlugin/ExamplePlugin.csproj.vspscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" 10 | } 11 | -------------------------------------------------------------------------------- /AutoHotkey.Interop/AutoHotkey.Interop.csproj.vspscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" 10 | } 11 | -------------------------------------------------------------------------------- /Toastify/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /InstallationScript/killproc_README.txt: -------------------------------------------------------------------------------- 1 | Copy to [NSIS install]\Plugins to use 2 | 3 | KillProcWMI , is an NSIS plugin based on the original KillProc plugin. KillProcWMI uses WMI to acheive the same results, which avoids problems with 32bit processes not being able to kill 64 bit processes. 4 | 5 | All Code is freely provided, no guarantees or warranties about its quality, use at your own risk. 6 | 7 | Jared Allen. 8 | ChironexSoftware.com -------------------------------------------------------------------------------- /Toastify/Spotify/SpotifyAction.cs: -------------------------------------------------------------------------------- 1 | namespace Toastify 2 | { 3 | public enum SpotifyAction : long 4 | { 5 | None = 0, 6 | ShowToast = 1, 7 | ShowSpotify = 2, 8 | CopyTrackInfo = 3, 9 | SettingsSaved = 4, 10 | PasteTrackInfo = 5, 11 | ThumbsUp = 6, // not usable, left in for future (hopefully!) 12 | ThumbsDown = 7, // not usable, left in for future (hopefully!) 13 | PlayPause = 917504, 14 | Mute = 524288, 15 | VolumeDown = 589824, 16 | VolumeUp = 655360, 17 | Stop = 851968, 18 | PreviousTrack = 786432, 19 | NextTrack = 720896, 20 | FastForward = 49 << 16, 21 | Rewind = 50 << 16, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Toastify/Toastify.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ProjectFiles 5 | publish\ 6 | http://toastify.codeplex.com/ 7 | 8 | 9 | 10 | 11 | en-US 12 | false 13 | 14 | 15 | false 16 | 17 | -------------------------------------------------------------------------------- /Toastify/BoolToVisibleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | using System.Windows; 4 | 5 | namespace Toastify 6 | { 7 | public class BoolToVisibleConverter : IValueConverter 8 | { 9 | 10 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 11 | { 12 | // Note: will throw a cast exception if you throw the wrong type at it. Good :) 13 | return ((bool)value ? Visibility.Visible : Visibility.Collapsed); 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Toastify/OppositeBoolConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | 4 | namespace Toastify 5 | { 6 | public class OppositeBoolConverter : IValueConverter 7 | { 8 | 9 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 10 | { 11 | // Note: don't check validity etc, since we'll need to throw an exception anyway, may as well let 12 | // that exception be here and not waste time :) 13 | return !(bool)value; 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Toastify/OppositeBoolToVisibleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | using System.Windows; 4 | 5 | namespace Toastify 6 | { 7 | public class OppositeBoolToVisibleConverter : IValueConverter 8 | { 9 | 10 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 11 | { 12 | // Note: will throw a cast exception if you throw the wrong type at it. Good :) 13 | return (!(bool)value ? Visibility.Visible : Visibility.Collapsed); 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PluginBase/PluginBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Toastify.Plugin 4 | { 5 | public interface IPluginBase : IDisposable 6 | { 7 | /// 8 | /// Is called directly after the constructor. 9 | /// 10 | /// Data from the Settings element in the xml. 11 | void Init(string settings); 12 | /// 13 | /// Is called when Toastify is first started. After Init(). 14 | /// 15 | void Started(); 16 | /// 17 | /// Is called when Toastify is closing. 18 | /// 19 | void Closing(); 20 | /// 21 | /// Is called on track change. 22 | /// 23 | /// 24 | /// 25 | void TrackChanged(string artist, string title); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Toastify/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Toastify.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /AutoHotkey.Interop/SafeLibraryHandle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Runtime.ConstrainedExecution; 4 | using System.Runtime.InteropServices; 5 | using System.Security; 6 | using System.Security.Permissions; 7 | 8 | namespace AutoHotkey.Interop 9 | { 10 | [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] 11 | internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid 12 | { 13 | #region Native Calls 14 | 15 | [DllImport("kernel32.dll", SetLastError = true)] 16 | public static extern SafeLibraryHandle LoadLibrary(string lpFileName); 17 | 18 | [SuppressUnmanagedCodeSecurity] 19 | [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] 20 | [DllImport("kernel32.dll", SetLastError = true)] 21 | private static extern bool FreeLibrary(IntPtr hModule); 22 | 23 | #endregion 24 | 25 | private SafeLibraryHandle() : base(true) { } 26 | 27 | protected override bool ReleaseHandle() 28 | { 29 | return FreeLibrary(handle); 30 | } 31 | 32 | protected override void Dispose(bool disposing) 33 | { 34 | base.Dispose(disposing); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Toastify/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Threading; 4 | 5 | namespace Toastify 6 | { 7 | //Special entry point to allow for single instance check 8 | public class EntryPoint 9 | { 10 | [STAThread] 11 | public static void Main(string[] args) 12 | { 13 | string appSpecificGuid = "{B8F3CA50-CE27-4ffa-A812-BBE1435C9485}"; 14 | using (Mutex m = new Mutex(true, appSpecificGuid, out bool exclusive)) 15 | { 16 | if (exclusive) 17 | { 18 | App app = new App(); 19 | app.InitializeComponent(); 20 | 21 | LastInputDebug.Start(); 22 | 23 | app.Run(); 24 | } 25 | else 26 | { 27 | MessageBox.Show("Toastify is already running!\n\nLook for the blue icon in your system tray.", "Toastify Already Running", MessageBoxButton.OK, MessageBoxImage.Information); 28 | } 29 | } 30 | } 31 | } 32 | 33 | /// 34 | /// Interaction logic for App.xaml 35 | /// 36 | public partial class App : Application 37 | { 38 | private void Application_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) 39 | { 40 | Telemetry.TrackException(e.Exception); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Toastify/ScreenHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Toastify 4 | { 5 | public static class ScreenHelper 6 | { 7 | 8 | private const int SCREEN_RIGHT_MARGIN = 5; 9 | private const int SCREEN_TOP_MARGIN = 5; 10 | 11 | public static Point GetDPIRatios() 12 | { 13 | var presentationSource = PresentationSource.FromVisual(Toast.Current); 14 | 15 | // if we hit this then Settings were loaded before the Toast window was 16 | System.Diagnostics.Debug.Assert(presentationSource != null); 17 | 18 | if (presentationSource == null) 19 | { 20 | // return a default dpi ratio of 1 (96dpi) 21 | return new Point(1, 1); 22 | } 23 | 24 | return new Point( 25 | presentationSource.CompositionTarget.TransformToDevice.M11, 26 | presentationSource.CompositionTarget.TransformToDevice.M22 27 | ); 28 | } 29 | 30 | public static System.Windows.Point GetDefaultToastPosition(double width, double height) 31 | { 32 | var screenRect = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea; 33 | 34 | var dpiRatio = GetDPIRatios(); 35 | 36 | return new System.Windows.Point 37 | ( 38 | (screenRect.Width / dpiRatio.X) - width - SCREEN_RIGHT_MARGIN, 39 | (screenRect.Height / dpiRatio.Y) - height - SCREEN_TOP_MARGIN 40 | ); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Toastify/Spotify/Song.cs: -------------------------------------------------------------------------------- 1 | namespace Toastify 2 | { 3 | public class Song 4 | { 5 | /// 6 | /// Is this a real Song or is Spotify not playing anything? 7 | /// 8 | /// Note: Window title doesn't change regardless of UI language 9 | /// 10 | public bool IsValid => 11 | (!string.IsNullOrEmpty(Artist) && Artist != "Spotify Free" && Artist != "Spotify Premium") || 12 | (!string.IsNullOrEmpty(Track)); 13 | 14 | public string Artist { get; set; } 15 | public string Track { get; set; } 16 | public string Album { get; set; } 17 | 18 | public string CoverArtUrl { get; set; } 19 | 20 | public Song(string artist, string title, string album = null) 21 | { 22 | Artist = artist; 23 | Track = title; 24 | Album = album; 25 | } 26 | 27 | public override string ToString() 28 | { 29 | if (Artist == null) 30 | return Track; 31 | 32 | return string.Format("{0} - {1}", Artist, Track); 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | if (!(obj is Song target)) 38 | return false; 39 | 40 | return (target.Artist == this.Artist && target.Track == this.Track); 41 | } 42 | 43 | // overriding GetHashCode is "required" when overriding Equals 44 | public override int GetHashCode() 45 | { 46 | return base.GetHashCode(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /AutoHotkey.Interop/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("AutoHotkey.Interop")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("AutoHotkey.Interop")] 12 | [assembly: AssemblyCopyright("Copyright © 2014")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("8a7cbbfe-ccb2-42ef-b90f-d937ae6ed741")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /PluginBase/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("PluginBase")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Microsoft")] 11 | [assembly: AssemblyProduct("PluginBase")] 12 | [assembly: AssemblyCopyright("Copyright © Microsoft 2009")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("d7229ff5-9d92-4bd3-876c-642ee9e4321e")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /ExamplePlugin/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("ExamplePlugin")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("Microsoft")] 11 | [assembly: AssemblyProduct("ExamplePlugin")] 12 | [assembly: AssemblyCopyright("Copyright © Microsoft 2009")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("1f81d14b-7a11-4a3f-92a9-d54530dabf00")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /Toastify/LastInputDebug.cs: -------------------------------------------------------------------------------- 1 | #if DEBUG 2 | 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using System.Threading; 6 | 7 | namespace Toastify 8 | { 9 | internal static class LastInputDebug 10 | { 11 | 12 | [StructLayout(LayoutKind.Sequential)] 13 | struct LASTINPUTINFO 14 | { 15 | public static readonly int SizeOf = Marshal.SizeOf(typeof(LASTINPUTINFO)); 16 | 17 | [MarshalAs(UnmanagedType.U4)] 18 | public UInt32 cbSize; 19 | [MarshalAs(UnmanagedType.U4)] 20 | public UInt32 dwTime; 21 | } 22 | 23 | [DllImport("user32.dll")] 24 | static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); 25 | 26 | public static void Start() 27 | { 28 | 29 | var t = new Thread(LastInputCheck); 30 | t.Start(); 31 | 32 | } 33 | 34 | private static void LastInputCheck() 35 | { 36 | var lastInputInfo = new LASTINPUTINFO(); 37 | lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo); 38 | 39 | while (true) 40 | { 41 | GetLastInputInfo(ref lastInputInfo); 42 | 43 | var idleTime = (uint)Environment.TickCount - lastInputInfo.dwTime; 44 | 45 | System.Diagnostics.Debug.WriteLine("Idle Time: " + ((idleTime > 0) ? (idleTime / 1000) : 0) + "secs (dwTime: " + lastInputInfo.dwTime + ")"); 46 | 47 | Thread.Sleep(10000); 48 | } 49 | } 50 | } 51 | } 52 | #else 53 | 54 | namespace Toastify 55 | { 56 | internal static class LastInputDebug 57 | { 58 | public static void Start() { } 59 | } 60 | } 61 | 62 | #endif -------------------------------------------------------------------------------- /Toastify/Spotify/SpotifyApiClient.cs: -------------------------------------------------------------------------------- 1 | using SpotifyAPI.Web; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace Toastify 6 | { 7 | internal static partial class SpotifyApiClient 8 | { 9 | // override these in SpotifyApiClient.private.cs. 10 | // If you don't have such a file, create one using the following template: 11 | // Note: missing " on purpose so that it doesn't build if you just dump it in :) 12 | // 13 | /* 14 | namespace Toastify 15 | { 16 | internal static partial class SpotifyApiClient 17 | { 18 | static SpotifyApiClient() 19 | { 20 | _CLIENT_ID = INSERT_REAL_VALUE; 21 | _CLIENT_SECRET = INSERT_REAL_VALUE; 22 | } 23 | } 24 | } 25 | */ 26 | private static readonly string _CLIENT_ID = "CHANGEME"; 27 | private static readonly string _CLIENT_SECRET = "CHANGEME"; 28 | 29 | private static readonly SpotifyClientConfig _spotifyClientConfig = SpotifyClientConfig.CreateDefault(); 30 | private static ClientCredentialsTokenResponse _tokenResponse; 31 | 32 | private static SpotifyClient _spotifyClient; 33 | 34 | public static async Task GetAsync() 35 | { 36 | 37 | if (_spotifyClient == null || _tokenResponse == null || _tokenResponse.IsExpired) 38 | { 39 | if (_CLIENT_ID == "CHANGEME" || _CLIENT_SECRET == "CHANGEME") 40 | { 41 | throw new Exception("You need to override _CLIENT_ID and _CLIENT_SECRET with the values from your Spotify dev account"); 42 | } 43 | 44 | var request = new ClientCredentialsRequest(_CLIENT_ID, _CLIENT_SECRET); 45 | _tokenResponse = await new OAuthClient(_spotifyClientConfig).RequestToken(request); 46 | 47 | _spotifyClient = new SpotifyClient(_spotifyClientConfig.WithToken(_tokenResponse.AccessToken)); 48 | } 49 | 50 | 51 | return _spotifyClient; 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ExamplePlugin/ExamplePlugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ExamplePlugin 4 | { 5 | //Sample Plugin. Simply logs the songs being played to a text file. 6 | //A reference is added to the ToastifyApi.dll(PluginBase) 7 | //The plugin class below implements the PluginBase interface 8 | 9 | 10 | //Toastify.xml 11 | // 12 | // 13 | // ExamplePlugin.dll Plugin filename 14 | // ExamplePlugin.ExamplePlugin/TypeName> Plugin type name (Namespace + "." + ClassName) 15 | // Plugin settings data 16 | // 17 | // 18 | 19 | public class ExamplePlugin : Toastify.Plugin.IPluginBase 20 | { 21 | public ExamplePlugin() 22 | { 23 | } 24 | 25 | string logFilename; 26 | public void Init(string settings) 27 | { 28 | //Init is called direcly after the constructor. 29 | //Any data from the element in Toastify.xml is passed on. 30 | //For multiple values, use a seperated list of some sort. Eg. "value1|value2|value3"... settings.Split('|')... 31 | 32 | this.logFilename = settings; 33 | } 34 | 35 | public void Started() 36 | { 37 | System.IO.File.AppendAllText(logFilename, "ExamplePlugin Started - " + DateTime.Now.ToLongTimeString() + Environment.NewLine); 38 | } 39 | 40 | public void Closing() 41 | { 42 | System.IO.File.AppendAllText(logFilename, "ExamplePlugin Closing - " + DateTime.Now.ToLongTimeString() + Environment.NewLine); 43 | } 44 | 45 | public void TrackChanged(string artist, string title) 46 | { 47 | System.IO.File.AppendAllText(logFilename, string.Format("{0} - {1}{2}", artist, title, Environment.NewLine)); 48 | } 49 | 50 | public void Dispose() 51 | { 52 | //Dispose of any resources here. 53 | //This is called direcly after Closing() 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Toastify/About.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using System.Windows.Documents; 4 | using System.Windows.Input; 5 | using System.Diagnostics; 6 | 7 | namespace Toastify 8 | { 9 | /// 10 | /// Interaction logic for About.xaml 11 | /// 12 | public partial class About : Window 13 | { 14 | readonly VersionChecker versionChecker; 15 | 16 | public About() 17 | { 18 | InitializeComponent(); 19 | 20 | versionChecker = new VersionChecker(); 21 | versionChecker.CheckVersionComplete += new EventHandler(versionChecker_CheckVersionComplete); 22 | 23 | this.DataContext = versionChecker; 24 | } 25 | 26 | void versionChecker_CheckVersionComplete(object sender, CheckVersionCompleteEventArgs e) 27 | { 28 | string latestVersionText = ""; 29 | 30 | if (string.IsNullOrEmpty(e.Version)) 31 | latestVersionText = "Unable to check version."; 32 | else if (e.New) 33 | latestVersionText = "New version available!"; 34 | else 35 | latestVersionText = "You have the latest version."; 36 | 37 | this.Dispatcher.Invoke(new Action(() => 38 | { 39 | Run run = new Run(latestVersionText); 40 | LatestVersion.Inlines.Clear(); 41 | LatestVersion.Inlines.Add(run); 42 | }), System.Windows.Threading.DispatcherPriority.Normal); 43 | } 44 | 45 | private void CodeplexLink_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) 46 | { 47 | System.Diagnostics.Process.Start("http://toastify.codeplex.com"); 48 | } 49 | 50 | private void Border_MouseUp(object sender, MouseButtonEventArgs e) 51 | { 52 | this.Close(); 53 | } 54 | 55 | private void Window_Loaded(object sender, RoutedEventArgs e) 56 | { 57 | versionChecker.BeginCheckVersion(); 58 | } 59 | 60 | private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) 61 | { 62 | Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri)); 63 | e.Handled = true; 64 | 65 | this.Close(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Toastify/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Toastify")] 9 | [assembly: AssemblyDescription("Toast for Spotify")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Jesper Palm")] 12 | [assembly: AssemblyProduct("Toastify")] 13 | [assembly: AssemblyCopyright("Copyright © 2009")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | //In order to begin building localizable applications, set 23 | //CultureYouAreCodingWith in your .csproj file 24 | //inside a . For example, if you are using US english 25 | //in your source files, set the to en-US. Then uncomment 26 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 27 | //the line below to match the UICulture setting in the project file. 28 | 29 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 30 | 31 | 32 | [assembly: ThemeInfo( 33 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 34 | //(used if a resource is not found in the page, 35 | // or application resource dictionaries) 36 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 37 | //(used if a resource is not found in the page, 38 | // app, or any theme specific resource dictionaries) 39 | )] 40 | 41 | 42 | // Version information for an assembly consists of the following four values: 43 | // 44 | // Major Version 45 | // Minor Version 46 | // Build Number 47 | // Revision 48 | // 49 | // You can specify all the values or you can default the Build and Revision Numbers 50 | // by using the '*' as shown below: 51 | // [assembly: AssemblyVersion("1.0.*")] 52 | [assembly: AssemblyVersion("1.8.3.*")] 53 | -------------------------------------------------------------------------------- /Toastify/VersionChecker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Text.RegularExpressions; 4 | using System.Threading; 5 | using System.Reflection; 6 | using System.Diagnostics; 7 | 8 | namespace Toastify 9 | { 10 | class VersionChecker 11 | { 12 | private static string _version; 13 | 14 | public static string Version 15 | { 16 | get 17 | { 18 | if (_version == null) 19 | { 20 | var assembly = Assembly.GetExecutingAssembly(); 21 | var fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location); 22 | 23 | _version = fileVersionInfo.FileVersion; 24 | 25 | var thirdDot = _version.LastIndexOf('.'); 26 | 27 | _version = _version.Substring(0, thirdDot); 28 | } 29 | 30 | return _version; 31 | } 32 | } 33 | 34 | public string UpdateUrl { get { return "https://toastify.codeplex.com/releases/view/24273"; } } 35 | 36 | readonly WebClient wc; 37 | 38 | public event EventHandler CheckVersionComplete; 39 | 40 | public VersionChecker() 41 | { 42 | wc = new WebClient(); 43 | wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted); 44 | } 45 | 46 | void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) 47 | { 48 | string version = string.Empty; 49 | bool newVersion = false; 50 | 51 | if (e.Cancelled == false && e.Error == null) 52 | { 53 | var match = Regex.Match(e.Result, "Version: (?[\\d+\\.]+)", RegexOptions.IgnoreCase | RegexOptions.Singleline); 54 | 55 | if (match.Success) 56 | { 57 | version = match.Groups["ver"].Value.Trim(); 58 | newVersion = Version != version; 59 | } 60 | } 61 | 62 | this.CheckVersionComplete?.Invoke(this, new CheckVersionCompleteEventArgs { Version = version, New = newVersion }); 63 | } 64 | 65 | public void BeginCheckVersion() 66 | { 67 | Thread t = new Thread(ThreadedBeginCheckVersion) 68 | { 69 | IsBackground = true 70 | }; 71 | t.Start(); 72 | } 73 | 74 | private void ThreadedBeginCheckVersion() 75 | { 76 | //WebClients XXXAsync isn't as async as I wanted... 77 | wc.DownloadStringAsync(new Uri("http://toastify.codeplex.com/wikipage?title=Version")); 78 | } 79 | } 80 | 81 | class CheckVersionCompleteEventArgs : EventArgs 82 | { 83 | public string Version { get; set; } 84 | public bool New { get; set; } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Toastify/WinHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Windows.Interop; 4 | 5 | namespace Toastify 6 | { 7 | class WinHelper 8 | { 9 | [Flags] 10 | public enum ExtendedWindowStyles 11 | { 12 | // ... 13 | WS_EX_TOOLWINDOW = 0x00000080, 14 | // ... 15 | } 16 | 17 | public enum GetWindowLongFields 18 | { 19 | // ... 20 | GWL_EXSTYLE = (-20), 21 | // ... 22 | } 23 | 24 | [DllImport("user32.dll")] 25 | private static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex); 26 | 27 | private static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong) 28 | { 29 | int error = 0; 30 | IntPtr result = IntPtr.Zero; 31 | // Win32 SetWindowLong doesn't clear error on success 32 | SetLastError(0); 33 | 34 | if (IntPtr.Size == 4) 35 | { 36 | // use SetWindowLong 37 | Int32 tempResult = IntSetWindowLong(hWnd, nIndex, IntPtrToInt32(dwNewLong)); 38 | error = Marshal.GetLastWin32Error(); 39 | result = new IntPtr(tempResult); 40 | } 41 | else 42 | { 43 | // use SetWindowLongPtr 44 | result = IntSetWindowLongPtr(hWnd, nIndex, dwNewLong); 45 | error = Marshal.GetLastWin32Error(); 46 | } 47 | 48 | if ((result == IntPtr.Zero) && (error != 0)) 49 | { 50 | throw new System.ComponentModel.Win32Exception(error); 51 | } 52 | 53 | return result; 54 | } 55 | 56 | [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)] 57 | private static extern IntPtr IntSetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); 58 | 59 | [DllImport("user32.dll", EntryPoint = "SetWindowLong", SetLastError = true)] 60 | private static extern Int32 IntSetWindowLong(IntPtr hWnd, int nIndex, Int32 dwNewLong); 61 | 62 | private static int IntPtrToInt32(IntPtr intPtr) 63 | { 64 | return unchecked((int)intPtr.ToInt64()); 65 | } 66 | 67 | [DllImport("kernel32.dll", EntryPoint = "SetLastError")] 68 | public static extern void SetLastError(int dwErrorCode); 69 | 70 | public static void AddToolWindowStyle(System.Windows.Window window) 71 | { 72 | WindowInteropHelper wndHelper = new WindowInteropHelper(window); 73 | int exStyle = (int)GetWindowLong(wndHelper.Handle, (int)WinHelper.GetWindowLongFields.GWL_EXSTYLE); 74 | exStyle |= (int)ExtendedWindowStyles.WS_EX_TOOLWINDOW; 75 | SetWindowLong(wndHelper.Handle, (int)GetWindowLongFields.GWL_EXSTYLE, (IntPtr)exStyle); 76 | } 77 | 78 | [FlagsAttribute] 79 | public enum EXECUTION_STATE : uint 80 | { 81 | ES_AWAYMODE_REQUIRED = 0x00000040, 82 | ES_CONTINUOUS = 0x80000000, 83 | ES_DISPLAY_REQUIRED = 0x00000002, 84 | ES_SYSTEM_REQUIRED = 0x00000001 85 | // Legacy flag, should not be used. 86 | // ES_USER_PRESENT = 0x00000004 87 | } 88 | 89 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 90 | public static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Toastify/Toast.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Body (feat. DJ Rush) 47 | Si Begg 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Toastify/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Toastify.Properties 12 | { 13 | using System; 14 | 15 | 16 | /// 17 | /// A strongly-typed resource class, for looking up localized strings, etc. 18 | /// 19 | // This class was auto-generated by the StronglyTypedResourceBuilder 20 | // class via a tool like ResGen or Visual Studio. 21 | // To add or remove a member, edit your .ResX file then rerun ResGen 22 | // with the /str option, or rebuild your VS project. 23 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 24 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 25 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 26 | internal class Resources 27 | { 28 | 29 | private static global::System.Resources.ResourceManager resourceMan; 30 | 31 | private static global::System.Globalization.CultureInfo resourceCulture; 32 | 33 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 34 | internal Resources() 35 | { 36 | } 37 | 38 | /// 39 | /// Returns the cached ResourceManager instance used by this class. 40 | /// 41 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 42 | internal static global::System.Resources.ResourceManager ResourceManager 43 | { 44 | get 45 | { 46 | if (object.ReferenceEquals(resourceMan, null)) 47 | { 48 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Toastify.Properties.Resources", typeof(Resources).Assembly); 49 | resourceMan = temp; 50 | } 51 | return resourceMan; 52 | } 53 | } 54 | 55 | /// 56 | /// Overrides the current thread's CurrentUICulture property for all 57 | /// resource lookups using this strongly typed resource class. 58 | /// 59 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 60 | internal static global::System.Globalization.CultureInfo Culture 61 | { 62 | get 63 | { 64 | return resourceCulture; 65 | } 66 | set 67 | { 68 | resourceCulture = value; 69 | } 70 | } 71 | 72 | /// 73 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 74 | /// 75 | internal static System.Drawing.Icon spotifyicon 76 | { 77 | get 78 | { 79 | object obj = ResourceManager.GetObject("spotifyicon", resourceCulture); 80 | return ((System.Drawing.Icon)(obj)); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /AutoHotkey.Interop/Util.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace AutoHotkey.Interop 7 | { 8 | internal static class Util 9 | { 10 | public static string FindEmbededResourceName(Assembly assembly, string path) 11 | { 12 | path = Regex.Replace(path, @"[/\\]", "."); 13 | 14 | if (!path.StartsWith(".")) 15 | path = "." + path; 16 | 17 | var names = assembly.GetManifestResourceNames(); 18 | 19 | foreach (var name in names) 20 | { 21 | if (name.EndsWith(path)) 22 | { 23 | return name; 24 | } 25 | } 26 | 27 | return null; 28 | } 29 | 30 | public static void ExtractEmbededResourceToFile(Assembly assembly, string embededResourcePath, string targetFileName) 31 | { 32 | //ensure directory exists 33 | var dir = Path.GetDirectoryName(targetFileName); 34 | 35 | if (!Directory.Exists(dir)) 36 | Directory.CreateDirectory(dir); 37 | 38 | using (var readStream = assembly.GetManifestResourceStream(embededResourcePath)) 39 | using (var writeStream = File.Open(targetFileName, FileMode.Create)) 40 | { 41 | readStream.CopyTo(writeStream); 42 | readStream.Flush(); 43 | } 44 | } 45 | 46 | public static bool Is64Bit() 47 | { 48 | return IntPtr.Size == 8; 49 | } 50 | public static bool Is32Bit() 51 | { 52 | return IntPtr.Size == 4; 53 | } 54 | 55 | 56 | 57 | public static void EnsureAutoHotkeyLoaded() 58 | { 59 | if (dllHandle.IsValueCreated) 60 | return; 61 | 62 | var handle = dllHandle.Value; 63 | } 64 | 65 | private static readonly Lazy dllHandle = new Lazy( 66 | () => Util.LoadAutoHotKeyDll()); 67 | private static SafeLibraryHandle LoadAutoHotKeyDll() 68 | { 69 | //Locate and Load 32bit or 64bit version of AutoHotkey.dll 70 | string tempFolderPath = Path.Combine(Path.GetTempPath(), "AutoHotkey.Interop"); 71 | string path32 = @"x86\AutoHotkey.dll"; 72 | string path64 = @"x64\AutoHotkey.dll"; 73 | 74 | var loadDllFromFileOrResource = new Func(relativePath => 75 | { 76 | if (File.Exists(relativePath)) 77 | { 78 | return SafeLibraryHandle.LoadLibrary(relativePath); 79 | } 80 | else 81 | { 82 | var assembly = typeof(AutoHotkeyEngine).Assembly; 83 | var resource = Util.FindEmbededResourceName(assembly, relativePath); 84 | 85 | if (resource != null) 86 | { 87 | var target = Path.Combine(tempFolderPath, relativePath); 88 | Util.ExtractEmbededResourceToFile(assembly, resource, target); 89 | return SafeLibraryHandle.LoadLibrary(target); 90 | } 91 | 92 | return null; 93 | } 94 | }); 95 | 96 | 97 | if (Util.Is32Bit()) 98 | { 99 | return loadDllFromFileOrResource(path32); 100 | } 101 | else if (Util.Is64Bit()) 102 | { 103 | return loadDllFromFileOrResource(path64); 104 | } 105 | else 106 | { 107 | return null; 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Toastify/About.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Toastify 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | toastify.codeplex.com 51 | 52 | Checking for updates... 53 | Credits to: Linus, Maaaackan, Aqualize, ncahmore... 54 | 55 | Created by Jesper Palm 2009, licensed under GPLv2. 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Toastify.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29709.97 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugin", "Plugin", "{64E51A59-02A2-4423-8DE1-F337D479E3B7}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installation", "Installation", "{FCD4BE87-8354-47A1-85DE-709E35559000}" 9 | ProjectSection(SolutionItems) = preProject 10 | InstallationScript\DotNET.nsh = InstallationScript\DotNET.nsh 11 | InstallationScript\Install.nsi = InstallationScript\Install.nsi 12 | EndProjectSection 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toastify", "Toastify\Toastify.csproj", "{CCC4A761-56D2-4188-807F-F7A547DFB031}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Toastify.Plugin", "PluginBase\Toastify.Plugin.csproj", "{4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExamplePlugin", "ExamplePlugin\ExamplePlugin.csproj", "{68F1A17D-4930-4EEC-AEEF-5DD9C9033B40}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoHotkey.Interop", "AutoHotkey.Interop\AutoHotkey.Interop.csproj", "{2869DBFE-7762-4930-95EA-2B0C111CF353}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Debug|x64 = Debug|x64 26 | Release|Any CPU = Release|Any CPU 27 | Release|x64 = Release|x64 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {CCC4A761-56D2-4188-807F-F7A547DFB031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {CCC4A761-56D2-4188-807F-F7A547DFB031}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {CCC4A761-56D2-4188-807F-F7A547DFB031}.Debug|x64.ActiveCfg = Debug|Any CPU 33 | {CCC4A761-56D2-4188-807F-F7A547DFB031}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {CCC4A761-56D2-4188-807F-F7A547DFB031}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {CCC4A761-56D2-4188-807F-F7A547DFB031}.Release|x64.ActiveCfg = Release|Any CPU 36 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8}.Debug|x64.ActiveCfg = Debug|Any CPU 39 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8}.Release|x64.ActiveCfg = Release|Any CPU 42 | {68F1A17D-4930-4EEC-AEEF-5DD9C9033B40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {68F1A17D-4930-4EEC-AEEF-5DD9C9033B40}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {68F1A17D-4930-4EEC-AEEF-5DD9C9033B40}.Debug|x64.ActiveCfg = Debug|Any CPU 45 | {68F1A17D-4930-4EEC-AEEF-5DD9C9033B40}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {68F1A17D-4930-4EEC-AEEF-5DD9C9033B40}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {68F1A17D-4930-4EEC-AEEF-5DD9C9033B40}.Release|x64.ActiveCfg = Release|Any CPU 48 | {2869DBFE-7762-4930-95EA-2B0C111CF353}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {2869DBFE-7762-4930-95EA-2B0C111CF353}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {2869DBFE-7762-4930-95EA-2B0C111CF353}.Debug|x64.ActiveCfg = Debug|x64 51 | {2869DBFE-7762-4930-95EA-2B0C111CF353}.Debug|x64.Build.0 = Debug|x64 52 | {2869DBFE-7762-4930-95EA-2B0C111CF353}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {2869DBFE-7762-4930-95EA-2B0C111CF353}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {2869DBFE-7762-4930-95EA-2B0C111CF353}.Release|x64.ActiveCfg = Release|x64 55 | {2869DBFE-7762-4930-95EA-2B0C111CF353}.Release|x64.Build.0 = Release|x64 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(NestedProjects) = preSolution 61 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8} = {64E51A59-02A2-4423-8DE1-F337D479E3B7} 62 | {68F1A17D-4930-4EEC-AEEF-5DD9C9033B40} = {64E51A59-02A2-4423-8DE1-F337D479E3B7} 63 | EndGlobalSection 64 | GlobalSection(ExtensibilityGlobals) = postSolution 65 | SolutionGuid = {0602AE4C-26D4-4C96-9DA1-BF2F49D20381} 66 | EndGlobalSection 67 | EndGlobal 68 | -------------------------------------------------------------------------------- /AutoHotkey.Interop/AutoHotkey.Interop.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2869DBFE-7762-4930-95EA-2B0C111CF353} 8 | Library 9 | Properties 10 | AutoHotkey.Interop 11 | AutoHotkey.Interop 12 | v4.0 13 | 512 14 | SAK 15 | SAK 16 | SAK 17 | SAK 18 | 19 | 20 | 21 | AnyCPU 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | AnyCPU 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | 41 | 42 | 43 | true 44 | bin\x64\Debug\ 45 | DEBUG;TRACE 46 | full 47 | x64 48 | prompt 49 | MinimumRecommendedRules.ruleset 50 | 51 | 52 | bin\x64\Release\ 53 | TRACE 54 | true 55 | pdbonly 56 | x64 57 | prompt 58 | MinimumRecommendedRules.ruleset 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Always 79 | 80 | 81 | Always 82 | 83 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /Toastify/Telemetry.cs: -------------------------------------------------------------------------------- 1 | using Garlic; 2 | using System; 3 | using System.Linq; 4 | using System.Management; 5 | 6 | namespace Toastify 7 | { 8 | public enum TelemetryCategory 9 | { 10 | General, 11 | Action, 12 | SpotifyWebService, 13 | } 14 | 15 | public static class Telemetry 16 | { 17 | private static AnalyticsSession _session; 18 | private static IAnalyticsPageViewRequest _client; 19 | 20 | static Telemetry() 21 | { 22 | Init(); 23 | } 24 | 25 | private static void Init() 26 | { 27 | _session = new AnalyticsSession("http://toastify.nachmore.com/app", "UA-61123985-2"); 28 | 29 | var settings = SettingsXml.Current; 30 | 31 | // abort asap if we are surpressing analytics 32 | if (settings.PreventAnalytics) 33 | return; 34 | 35 | _session.SetCustomVariable(1, "OS Version", GetOS()); 36 | 37 | _client = _session.CreatePageViewRequest("/", "Global"); 38 | 39 | if (SettingsXml.Current.FirstRun) 40 | { 41 | TrackEvent(TelemetryCategory.General, "Install", GetOS()); 42 | 43 | SettingsXml.Current.FirstRun = false; 44 | } 45 | } 46 | 47 | public static void TrackEvent(TelemetryCategory category, string action, object label = null, int value = 0) 48 | { 49 | if (_client != null) 50 | _client.SendEvent(category.ToString(), action, (label != null ? label.ToString() : null), value.ToString()); 51 | } 52 | 53 | internal static void TrackException(Exception exception) 54 | { 55 | // The exception will be truncated to 500bytes (GA limit for Labels), at some point it may be better to extract more pertinant information 56 | TrackEvent(TelemetryCategory.General, TelemetryEvent.Exception, exception.ToString()); 57 | } 58 | 59 | internal static void TrackEvent(TelemetryCategory general, object settingsLaunched) 60 | { 61 | throw new NotImplementedException(); 62 | } 63 | 64 | public static string GetOS() 65 | { 66 | return Environment.OSVersion.VersionString + 67 | " (" + GetFriendlyOS() + ")" + 68 | " (" + (Environment.Is64BitOperatingSystem ? "x64" : "x86") + ")"; 69 | } 70 | 71 | private static string GetFriendlyOS() 72 | { 73 | var name = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType() 74 | select x.GetPropertyValue("Caption")).FirstOrDefault(); 75 | return name != null ? name.ToString() : "Unknown"; 76 | } 77 | 78 | /// 79 | /// Poor mans enum -> expanded string. 80 | /// 81 | /// Once I've been using this for a while I may change this to a pure enum if 82 | /// spaces in names prove to be annoying for querying / sorting the data 83 | /// 84 | public static class TelemetryEvent 85 | { 86 | public const string Exception = "Toastify.General.Exception"; 87 | 88 | public const string AppLaunch = "Toastify.General.AppLaunched"; 89 | public const string AppUpgraded = "Toastify.General.AppUpgraded"; 90 | 91 | public const string SettingsLaunched = "Toastify.General.SettingsLaunched"; 92 | 93 | public static class SpotifyWebService 94 | { 95 | public const string NetworkError = "Toastify.SpotifyWebService.NetworkError"; 96 | public const string ResponseError = "Toastify.SpotifyWebService.ResponseError"; 97 | } 98 | 99 | public static class Action 100 | { 101 | public const string Mute = "Toastify.Action.Mute"; 102 | public const string VolumeDown = "Toastify.Action.VolumeDown"; 103 | public const string VolumeUp = "Toasitfy.Action.VolumeUp"; 104 | public const string ShowToast = "Toastify.Action.ShowToast"; 105 | public const string ShowSpotify = "Toastify.Action.ShowSpotify"; 106 | public const string CopyTrackInfo = "Toastify.Action.CopyTrackInfo"; 107 | public const string PasteTrackInfo = "Toastify.Action.PasteTrackInfo"; 108 | public const string FastForward = "Toastify.Action.FastForward"; 109 | public const string Rewind = "Toastify.Action.Rewind"; 110 | public const string Default = "Toastify.Action."; 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /InstallationScript/Install.nsi: -------------------------------------------------------------------------------- 1 | !include "DotNET.nsh" 2 | !include LogicLib.nsh 3 | !define DOTNET_VERSION "3.5" 4 | !include "MUI.nsh" 5 | 6 | ; The name of the installer 7 | Name "Toastify Installer" 8 | 9 | ; The file to write 10 | OutFile "ToastifyInstaller.exe" 11 | 12 | ; The default installation directory 13 | InstallDir $PROGRAMFILES\Toastify 14 | 15 | ; Request application privileges for Windows Vista 16 | RequestExecutionLevel admin 17 | 18 | ;-------------------------------- 19 | 20 | ; Pages 21 | 22 | Page components 23 | Page directory 24 | Page instfiles 25 | 26 | # These indented statements modify settings for MUI_PAGE_FINISH 27 | !define MUI_FINISHPAGE_AUTOCLOSE 28 | !define MUI_FINISHPAGE_RUN 29 | !define MUI_FINISHPAGE_RUN_CHECKED 30 | !define MUI_FINISHPAGE_RUN_TEXT "Launch Toastify Now" 31 | !define MUI_FINISHPAGE_RUN_FUNCTION "LaunchLink" 32 | !insertmacro MUI_PAGE_FINISH 33 | 34 | ;Languages 35 | !insertmacro MUI_LANGUAGE "English" 36 | 37 | ;-------------------------------- 38 | Function LaunchLink 39 | ExecShell "" "$INSTDIR\Toastify.exe" 40 | FunctionEnd 41 | 42 | UninstPage uninstConfirm 43 | UninstPage instfiles 44 | 45 | ;-------------------------------- 46 | 47 | Section "Toastify (required)" 48 | SectionIn RO 49 | 50 | ; Since process termination is non-destructive for Toastify, just kill it 51 | DetailPrint "Shutting down Toastify..." 52 | KillProcWMI::KillProc "Toastify.exe" 53 | 54 | ; Let the process shutdown 55 | Sleep 1000 56 | 57 | ; Set output path to the installation directory. 58 | SetOutPath $INSTDIR 59 | 60 | ; Put file there 61 | File "Toastify.exe" 62 | File "ToastifyApi.dll" 63 | File "ManagedWinapi.dll" 64 | File "Resources\ManagedWinapiNativeHelper.dll" 65 | File "AutoHotkey.Interop.dll" 66 | File "Garlic.dll" 67 | File "Newtonsoft.Json.dll" 68 | File "LICENSE" 69 | 70 | ; Write the uninstall keys for Windows 71 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" "DisplayName" "Toastify" 72 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" "UninstallString" '"$INSTDIR\uninstall.exe"' 73 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" "DisplayIcon" "$INSTDIR\Toastify.exe,0" 74 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" "Publisher" "Jesper Palm" 75 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" "Version" "1.6" 76 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" "DisplayVersion" "1.6" 77 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" "NoModify" 1 78 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" "NoRepair" 1 79 | WriteUninstaller "uninstall.exe" 80 | SectionEnd 81 | 82 | Section "Desktop icon" 83 | CreateShortCut "$DESKTOP\Toastify.lnk" "$INSTDIR\Toastify.exe" "" "$INSTDIR\Toastify.exe" 0 84 | SectionEnd 85 | 86 | Section "Start Menu icon" 87 | # Start Menu 88 | CreateShortCut "$SMPROGRAMS\Toastify.lnk" "$INSTDIR\Toastify.exe" "" "$INSTDIR\Toastify.exe" 0 89 | 90 | SectionEnd 91 | 92 | Section "Autostart" 93 | WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Toastify" '"$INSTDIR\Toastify.exe"' 94 | SectionEnd 95 | 96 | ;-------------------------------- 97 | 98 | ; Uninstaller 99 | 100 | Section "Uninstall" 101 | 102 | # Remove Start Menu launcher 103 | Delete "$SMPROGRAMS\Toastify.lnk" 104 | 105 | ; Remove registry keys 106 | DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Toastify" 107 | DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "Toastify" 108 | 109 | ; Remove files and uninstaller 110 | Delete "$INSTDIR\Toastify.exe" 111 | Delete "$INSTDIR\ToastifyApi.dll" 112 | Delete "$INSTDIR\ManagedWinapi.dll" 113 | Delete "$INSTDIR\ManagedWinapiNativeHelper.dll" 114 | Delete "$INSTDIR\AutoHotkey.Interop.dll" 115 | Delete "$INSTDIR\Garlic.dll" 116 | Delete "$INSTDIR\Newtonsoft.Json.dll" 117 | Delete "$INSTDIR\LICENSE" 118 | 119 | ; remove the settings directory 120 | Delete "$APPDATA\Toastify.xml" 121 | RMDir "$APPDATA\Toastify" 122 | 123 | ; Remove shortcuts, if any 124 | Delete "$DESKTOP\Toastify.lnk" 125 | 126 | ; Remove directories used 127 | RMDir "$INSTDIR" 128 | SectionEnd 129 | 130 | Function .onInit 131 | FunctionEnd 132 | -------------------------------------------------------------------------------- /PluginBase/Toastify.Plugin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8} 9 | Library 10 | Properties 11 | Toastify.Plugin 12 | ToastifyApi 13 | v4.6.1 14 | 512 15 | 16 | 17 | 3.5 18 | 19 | publish\ 20 | true 21 | Disk 22 | false 23 | Foreground 24 | 7 25 | Days 26 | false 27 | false 28 | true 29 | 0 30 | 1.0.0.%2a 31 | false 32 | false 33 | true 34 | SAK 35 | SAK 36 | SAK 37 | SAK 38 | 39 | 40 | 41 | true 42 | full 43 | false 44 | bin\Debug\ 45 | DEBUG;TRACE 46 | prompt 47 | 4 48 | AllRules.ruleset 49 | false 50 | 51 | 52 | pdbonly 53 | true 54 | bin\Release\ 55 | TRACE 56 | prompt 57 | 4 58 | AllRules.ruleset 59 | false 60 | 61 | 62 | 63 | 64 | 3.5 65 | 66 | 67 | 3.5 68 | 69 | 70 | 3.5 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | False 82 | .NET Framework 3.5 SP1 Client Profile 83 | false 84 | 85 | 86 | False 87 | .NET Framework 3.5 SP1 88 | true 89 | 90 | 91 | False 92 | Windows Installer 3.1 93 | true 94 | 95 | 96 | 97 | 104 | -------------------------------------------------------------------------------- /ExamplePlugin/ExamplePlugin.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {68F1A17D-4930-4EEC-AEEF-5DD9C9033B40} 9 | Library 10 | Properties 11 | ExamplePlugin 12 | ExamplePlugin 13 | v4.6.1 14 | 512 15 | 16 | 17 | 3.5 18 | 19 | publish\ 20 | true 21 | Disk 22 | false 23 | Foreground 24 | 7 25 | Days 26 | false 27 | false 28 | true 29 | 0 30 | 1.0.0.%2a 31 | false 32 | false 33 | true 34 | SAK 35 | SAK 36 | SAK 37 | SAK 38 | 39 | 40 | 41 | true 42 | full 43 | false 44 | bin\Debug\ 45 | DEBUG;TRACE 46 | prompt 47 | 4 48 | AllRules.ruleset 49 | false 50 | 51 | 52 | pdbonly 53 | true 54 | bin\Release\ 55 | TRACE 56 | prompt 57 | 4 58 | AllRules.ruleset 59 | false 60 | 61 | 62 | 63 | 64 | 3.5 65 | 66 | 67 | 3.5 68 | 69 | 70 | 3.5 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | {4F92BE1F-A5CC-4604-9185-1F09DDAFC7B8} 82 | Toastify.Plugin 83 | 84 | 85 | 86 | 87 | False 88 | .NET Framework 3.5 SP1 Client Profile 89 | false 90 | 91 | 92 | False 93 | .NET Framework 3.5 SP1 94 | true 95 | 96 | 97 | False 98 | Windows Installer 3.1 99 | true 100 | 101 | 102 | 103 | 110 | -------------------------------------------------------------------------------- /AutoHotkey.Interop/AutoHotkeyEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AutoHotkey.Interop 5 | { 6 | /// 7 | /// This class expects an AutoHotkey.dll to be available on the machine. (UNICODE) version. 8 | /// 9 | public class AutoHotkeyEngine 10 | { 11 | public AutoHotkeyEngine() 12 | { 13 | Util.EnsureAutoHotkeyLoaded(); 14 | 15 | //ensure that a thread is started 16 | AutoHotkeyDll.ahktextdll("", "", ""); 17 | } 18 | 19 | /// 20 | /// Gets the value for a varible or an empty string if the variable does not exist. 21 | /// 22 | /// Name of the variable. 23 | /// Returns the value of the variable, or an empty string if the variable does not exist. 24 | public string GetVar(string variableName) 25 | { 26 | var p = AutoHotkeyDll.ahkgetvar(variableName, 0); 27 | return Marshal.PtrToStringUni(p); 28 | } 29 | 30 | /// 31 | /// Sets the value of a variable. 32 | /// 33 | /// Name of the variable. 34 | /// The value to set. 35 | public void SetVar(string variableName, string value) 36 | { 37 | if (value == null) 38 | value = ""; 39 | 40 | AutoHotkeyDll.ahkassign(variableName, value); 41 | } 42 | 43 | /// 44 | /// Evaulates an expression or function and returns the results 45 | /// 46 | /// The code to execute 47 | /// Returns the result of an expression 48 | public string Eval(string code) 49 | { 50 | var codeToRun = "A__EVAL:=" + code; 51 | AutoHotkeyDll.ahkExec(codeToRun); 52 | return GetVar("A__EVAL"); 53 | } 54 | 55 | /// 56 | /// Loads a file into the running script 57 | /// 58 | /// The filepath of the script 59 | public void Load(string filePath) 60 | { 61 | AutoHotkeyDll.addFile(filePath, 1, 1); 62 | } 63 | 64 | /// 65 | /// Executes raw ahk code. 66 | /// 67 | /// The code to execute 68 | public void ExecRaw(string code) 69 | { 70 | AutoHotkeyDll.ahkExec(code); 71 | } 72 | 73 | /// 74 | /// Terminates the running scripts 75 | /// 76 | public void Terminate() 77 | { 78 | AutoHotkeyDll.ahkTerminate(1000); 79 | } 80 | 81 | /// 82 | /// Suspends the scripts 83 | /// 84 | public void Suspend() 85 | { 86 | ExecRaw("Suspend, On"); 87 | } 88 | 89 | /// 90 | /// Unsuspends the scripts 91 | /// 92 | public void UnSuspend() 93 | { 94 | ExecRaw("Suspend, Off"); 95 | } 96 | 97 | /// 98 | /// Executes an already defined function. 99 | /// 100 | /// The name of the function to execute. 101 | /// The 1st parameter 102 | /// The 2nd parameter 103 | /// The 3rd parameter 104 | /// The 4th parameter 105 | /// The 5th parameter 106 | /// The 6th parameter 107 | /// The 7th parameter 108 | /// The 8th parameter 109 | /// The 9th parameter 110 | /// The 10 parameter 111 | public string ExecFunction(string functionName, 112 | string param1 = null, 113 | string param2 = null, 114 | string param3 = null, 115 | string param4 = null, 116 | string param5 = null, 117 | string param6 = null, 118 | string param7 = null, 119 | string param8 = null, 120 | string param9 = null, 121 | string param10 = null) 122 | { 123 | IntPtr ret = AutoHotkeyDll.ahkFunction(functionName, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); 124 | 125 | if (ret == IntPtr.Zero) 126 | return null; 127 | else 128 | return Marshal.PtrToStringUni(ret); 129 | } 130 | 131 | 132 | /// 133 | /// Determines if the function exists. 134 | /// 135 | /// Name of the function. 136 | /// Returns true if the function exists, otherwise false. 137 | public bool FunctionExists(string functionName) 138 | { 139 | IntPtr funcptr = AutoHotkeyDll.ahkFindFunc(functionName); 140 | return funcptr != IntPtr.Zero; 141 | } 142 | 143 | /// 144 | /// Executes a label 145 | /// 146 | /// Name of the label. 147 | public void ExecLabel(string labelName) 148 | { 149 | AutoHotkeyDll.ahkLabel(labelName, false); 150 | } 151 | 152 | /// 153 | /// Determines if the label exists. 154 | /// 155 | /// Name of the label. 156 | /// Returns true if the label exists, otherwise false 157 | public bool LabelExists(string labelName) 158 | { 159 | IntPtr labelptr = AutoHotkeyDll.ahkFindLabel(labelName); 160 | return labelptr != IntPtr.Zero; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Toastify/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\spotify.ico;System.Drawing.Icon, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /InstallationScript/DotNET.nsh: -------------------------------------------------------------------------------- 1 | # DotNET version checking macro. 2 | # Written by AnarkiNet(AnarkiNet@gmail.com) originally, modified by eyal0 (for use in http://www.sourceforge.net/projects/itwister) 3 | # Downloads and runs the Microsoft .NET Framework version 2.0 Redistributable and runs it if the user does not have the correct version. 4 | # To use, call the macro with a string: 5 | # !insertmacro CheckDotNET "2" 6 | # !insertmacro CheckDotNET "2.0.9" 7 | # (Version 2.0.9 is less than version 2.0.10.) 8 | # All register variables are saved and restored by CheckDotNet 9 | # No output 10 | 11 | !macro CheckDotNET DotNetReqVer 12 | !define DOTNET_URL "http://www.microsoft.com/downloads/info.aspx?na=90&p=&SrcDisplayLang=en&SrcCategoryId=&SrcFamilyId=0856eacb-4362-4b0d-8edd-aab15c5e04f5&u=http%3a%2f%2fdownload.microsoft.com%2fdownload%2f5%2f6%2f7%2f567758a3-759e-473e-bf8f-52154438565a%2fdotnetfx.exe" 13 | DetailPrint "Checking your .NET Framework version..." 14 | ;callee register save 15 | Push $0 16 | Push $1 17 | Push $2 18 | Push $3 19 | Push $4 20 | Push $5 21 | Push $6 ;backup of intsalled ver 22 | Push $7 ;backup of DoNetReqVer 23 | 24 | StrCpy $7 ${DotNetReqVer} 25 | 26 | System::Call "mscoree::GetCORVersion(w .r0, i ${NSIS_MAX_STRLEN}, *i r2r2) i .r1 ?u" 27 | 28 | ${If} $0 == 0 29 | DetailPrint ".NET Framework not found, download is required for program to run." 30 | Goto NoDotNET 31 | ${EndIf} 32 | 33 | DetailPrint $0 34 | 35 | ;at this point, $0 has maybe v2.345.678. 36 | StrCpy $0 $0 $2 1 ;remove the starting "v", $0 has the installed version num as a string 37 | StrCpy $6 $0 38 | StrCpy $1 $7 ;$1 has the requested verison num as a string 39 | 40 | ;MessageBox MB_OKCANCEL "found $0" IDCANCEL GiveUpDotNET 41 | 42 | ;MessageBox MB_OKCANCEL "looking for $1" IDCANCEL GiveUpDotNET 43 | 44 | ;now let's compare the versions, installed against required ... 45 | ${Do} 46 | StrCpy $2 "" ;clear out the installed part 47 | StrCpy $3 "" ;clear out the required part 48 | 49 | ${Do} 50 | ${If} $0 == "" ;if there are no more characters in the version 51 | StrCpy $4 "." ;fake the end of the version string 52 | ${Else} 53 | StrCpy $4 $0 1 0 ;$4 = character from the installed ver 54 | ${If} $4 != "." 55 | StrCpy $0 $0 ${NSIS_MAX_STRLEN} 1 ;remove that first character from the remaining 56 | ${EndIf} 57 | ${EndIf} 58 | 59 | ${If} $1 == "" ;if there are no more characters in the version 60 | StrCpy $5 "." ;fake the end of the version string 61 | ${Else} 62 | StrCpy $5 $1 1 0 ;$5 = character from the required ver 63 | ${If} $5 != "." 64 | StrCpy $1 $1 ${NSIS_MAX_STRLEN} 1 ;remove that first character from the remaining 65 | ${EndIf} 66 | ${EndIf} 67 | ;MessageBox MB_OKCANCEL "installed $2,$4,$0 required $3,$5,$1" IDCANCEL GiveUpDotNET 68 | ${If} $4 == "." 69 | ${AndIf} $5 == "." 70 | ${ExitDo} ;we're at the end of the part 71 | ${EndIf} 72 | 73 | ${If} $4 == "." ;if we're at the end of the current installed part 74 | StrCpy $2 "0$2" ;put a zero on the front 75 | ${Else} ;we have another character 76 | StrCpy $2 "$2$4" ;put the next character on the back 77 | ${EndIf} 78 | ${If} $5 == "." ;if we're at the end of the current required part 79 | StrCpy $3 "0$3" ;put a zero on the front 80 | ${Else} ;we have another character 81 | StrCpy $3 "$3$5" ;put the next character on the back 82 | ${EndIf} 83 | ${Loop} 84 | ;MessageBox MB_OKCANCEL "finished parts: installed $2,$4,$0 required $3,$5,$1" IDCANCEL GiveUpDotNET 85 | 86 | ${If} $0 != "" ;let's remove the leading period on installed part if it exists 87 | StrCpy $0 $0 ${NSIS_MAX_STRLEN} 1 88 | ${EndIf} 89 | ${If} $1 != "" ;let's remove the leading period on required part if it exists 90 | StrCpy $1 $1 ${NSIS_MAX_STRLEN} 1 91 | ${EndIf} 92 | 93 | ;$2 has the installed part, $3 has the required part 94 | ${If} $2 S< $3 95 | IntOp $0 0 - 1 ;$0 = -1, installed less than required 96 | ${ExitDo} 97 | ${ElseIf} $2 S> $3 98 | IntOp $0 0 + 1 ;$0 = 1, installed greater than required 99 | ${ExitDo} 100 | ${ElseIf} $2 == "" 101 | ${AndIf} $3 == "" 102 | IntOp $0 0 + 0 ;$0 = 0, the versions are identical 103 | ${ExitDo} 104 | ${EndIf} ;otherwise we just keep looping through the parts 105 | ${Loop} 106 | 107 | ${If} $0 < 0 108 | DetailPrint ".NET Framework Version found: $6, but is older than the required version: $7" 109 | Goto OldDotNET 110 | ${Else} 111 | DetailPrint ".NET Framework Version found: $6, equal or newer to required version: $7." 112 | Goto NewDotNET 113 | ${EndIf} 114 | 115 | NoDotNET: 116 | MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION \ 117 | ".NET Framework not installed.$\nRequired Version: $7 or greater.$\nDownload .NET Framework version from www.microsoft.com?" \ 118 | /SD IDYES IDYES DownloadDotNET IDNO NewDotNET 119 | goto GiveUpDotNET ;IDCANCEL 120 | OldDotNET: 121 | MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION \ 122 | "Your .NET Framework version: $6.$\nRequired Version: $7 or greater.$\nDownload .NET Framework version from www.microsoft.com?" \ 123 | /SD IDYES IDYES DownloadDotNET IDNO NewDotNET 124 | goto GiveUpDotNET ;IDCANCEL 125 | 126 | DownloadDotNET: 127 | DetailPrint "Beginning download of latest .NET Framework version." 128 | NSISDL::download ${DOTNET_URL} "$TEMP\dotnetfx.exe" 129 | DetailPrint "Completed download." 130 | Pop $0 131 | ${If} $0 == "cancel" 132 | MessageBox MB_YESNO|MB_ICONEXCLAMATION \ 133 | "Download cancelled. Continue Installation?" \ 134 | IDYES NewDotNET IDNO GiveUpDotNET 135 | ${ElseIf} $0 != "success" 136 | MessageBox MB_YESNO|MB_ICONEXCLAMATION \ 137 | "Download failed:$\n$0$\n$\nContinue Installation?" \ 138 | IDYES NewDotNET IDNO GiveUpDotNET 139 | ${EndIf} 140 | DetailPrint "Pausing installation while downloaded .NET Framework installer runs." 141 | ExecWait '$TEMP\dotnetfx.exe /q /c:"install /q"' 142 | DetailPrint "Completed .NET Framework install/update. Removing .NET Framework installer." 143 | Delete "$TEMP\dotnetfx.exe" 144 | DetailPrint ".NET Framework installer removed." 145 | goto NewDotNet 146 | 147 | GiveUpDotNET: 148 | Abort "Installation cancelled by user." 149 | 150 | NewDotNET: 151 | DetailPrint "Proceeding with remainder of installation." 152 | Pop $0 153 | Pop $1 154 | Pop $2 155 | Pop $3 156 | Pop $4 157 | Pop $5 158 | Pop $6 ;backup of intsalled ver 159 | Pop $7 ;backup of DoNetReqVer 160 | !macroend -------------------------------------------------------------------------------- /BuildProcessTemplates/UpgradeTemplate.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | [New Microsoft.TeamFoundation.Build.Workflow.Activities.AgentSettings() With {.MaxWaitTime = New System.TimeSpan(4, 0, 0), .MaxExecutionTime = New System.TimeSpan(0, 0, 0), .TagComparison = Microsoft.TeamFoundation.Build.Workflow.Activities.TagComparison.MatchExactly }] 21 | 22 | 23 | 24 | [Microsoft.TeamFoundation.Build.Workflow.Activities.ToolPlatform.Auto] 25 | [False] 26 | [False] 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | [Microsoft.TeamFoundation.VersionControl.Client.RecursionType.OneLevel] 37 | [Microsoft.TeamFoundation.Build.Workflow.BuildVerbosity.Normal] 38 | 39 | 40 | 41 | All 42 | Assembly references and imported namespaces serialized as XML namespaces 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Toastify/Win32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace Toastify 6 | { 7 | internal class Win32 8 | { 9 | [DllImport("user32.dll", SetLastError = true)] 10 | internal static extern IntPtr FindWindow(string lpClassName, string lpWindowName); 11 | 12 | internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); 13 | 14 | [DllImport("user32.dll")] 15 | internal static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); 16 | 17 | [DllImport("user32", SetLastError = true)] 18 | [return: MarshalAs(UnmanagedType.Bool)] 19 | internal static extern bool EnumThreadWindows(int threadId, EnumWindowsProc callback, IntPtr lParam); 20 | 21 | [DllImport("user32.dll")] 22 | internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); 23 | 24 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 25 | internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); 26 | 27 | [DllImport("user32.dll", CharSet = CharSet.Auto)] 28 | internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); 29 | 30 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 31 | internal static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); 32 | 33 | [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] 34 | internal static extern int GetWindowTextLength(IntPtr hWnd); 35 | 36 | [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] 37 | internal static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); 38 | 39 | [DllImport("user32.dll")] 40 | internal static extern IntPtr SetFocus(IntPtr hWnd); 41 | 42 | [DllImport("user32.dll")] 43 | internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); 44 | 45 | [DllImport("user32.dll")] 46 | internal static extern bool SetForegroundWindow(IntPtr hWnd); 47 | 48 | [DllImport("user32.dll")] 49 | internal static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); 50 | 51 | [DllImport("user32.dll", SetLastError = true)] 52 | internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags); 53 | 54 | [Flags()] 55 | internal enum SetWindowPosFlags : uint 56 | { 57 | /// If the calling thread and the thread that owns the window are attached to different input queues, 58 | /// the system posts the request to the thread that owns the window. This prevents the calling thread from 59 | /// blocking its execution while other threads process the request. 60 | /// SWP_ASYNCWINDOWPOS 61 | AsynchronousWindowPosition = 0x4000, 62 | /// Prevents generation of the WM_SYNCPAINT message. 63 | /// SWP_DEFERERASE 64 | DeferErase = 0x2000, 65 | /// Draws a frame (defined in the window's class description) around the window. 66 | /// SWP_DRAWFRAME 67 | DrawFrame = 0x0020, 68 | /// Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to 69 | /// the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE 70 | /// is sent only when the window's size is being changed. 71 | /// SWP_FRAMECHANGED 72 | FrameChanged = 0x0020, 73 | /// Hides the window. 74 | /// SWP_HIDEWINDOW 75 | HideWindow = 0x0080, 76 | /// Does not activate the window. If this flag is not set, the window is activated and moved to the 77 | /// top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter 78 | /// parameter). 79 | /// SWP_NOACTIVATE 80 | DoNotActivate = 0x0010, 81 | /// Discards the entire contents of the client area. If this flag is not specified, the valid 82 | /// contents of the client area are saved and copied back into the client area after the window is sized or 83 | /// repositioned. 84 | /// SWP_NOCOPYBITS 85 | DoNotCopyBits = 0x0100, 86 | /// Retains the current position (ignores X and Y parameters). 87 | /// SWP_NOMOVE 88 | IgnoreMove = 0x0002, 89 | /// Does not change the owner window's position in the Z order. 90 | /// SWP_NOOWNERZORDER 91 | DoNotChangeOwnerZOrder = 0x0200, 92 | /// Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to 93 | /// the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent 94 | /// window uncovered as a result of the window being moved. When this flag is set, the application must 95 | /// explicitly invalidate or redraw any parts of the window and parent window that need redrawing. 96 | /// SWP_NOREDRAW 97 | DoNotRedraw = 0x0008, 98 | /// Same as the SWP_NOOWNERZORDER flag. 99 | /// SWP_NOREPOSITION 100 | DoNotReposition = 0x0200, 101 | /// Prevents the window from receiving the WM_WINDOWPOSCHANGING message. 102 | /// SWP_NOSENDCHANGING 103 | DoNotSendChangingEvent = 0x0400, 104 | /// Retains the current size (ignores the cx and cy parameters). 105 | /// SWP_NOSIZE 106 | IgnoreResize = 0x0001, 107 | /// Retains the current Z order (ignores the hWndInsertAfter parameter). 108 | /// SWP_NOZORDER 109 | IgnoreZOrder = 0x0004, 110 | /// Displays the window. 111 | /// SWP_SHOWWINDOW 112 | ShowWindow = 0x0040, 113 | } 114 | 115 | internal struct WINDOWPLACEMENT 116 | { 117 | public int length; 118 | public int flags; 119 | public int showCmd; 120 | public System.Drawing.Point ptMinPosition; 121 | public System.Drawing.Point ptMaxPosition; 122 | public System.Drawing.Rectangle rcNormalPosition; 123 | } 124 | 125 | internal class Constants 126 | { 127 | internal const uint WM_APPCOMMAND = 0x0319; 128 | 129 | internal const int SW_SHOWMINIMIZED = 2; 130 | internal const int SW_SHOWNOACTIVATE = 4; 131 | internal const int SW_SHOWMINNOACTIVE = 7; 132 | internal const int SW_SHOW = 5; 133 | internal const int SW_RESTORE = 9; 134 | 135 | internal const int WM_CLOSE = 0x10; 136 | internal const int WM_QUIT = 0x12; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore private files 2 | 3 | *.private.* 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 9 | 10 | # User-specific files 11 | *.rsuser 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Mono auto generated files 21 | mono_crash.* 22 | 23 | # Build results 24 | [Dd]ebug/ 25 | [Dd]ebugPublic/ 26 | [Rr]elease/ 27 | [Rr]eleases/ 28 | x64/ 29 | x86/ 30 | [Aa][Rr][Mm]/ 31 | [Aa][Rr][Mm]64/ 32 | bld/ 33 | [Bb]in/ 34 | [Oo]bj/ 35 | [Ll]og/ 36 | [Ll]ogs/ 37 | 38 | # Visual Studio 2015/2017 cache/options directory 39 | .vs/ 40 | # Uncomment if you have tasks that create the project's static files in wwwroot 41 | #wwwroot/ 42 | 43 | # Visual Studio 2017 auto generated files 44 | Generated\ Files/ 45 | 46 | # MSTest test Results 47 | [Tt]est[Rr]esult*/ 48 | [Bb]uild[Ll]og.* 49 | 50 | # NUnit 51 | *.VisualState.xml 52 | TestResult.xml 53 | nunit-*.xml 54 | 55 | # Build Results of an ATL Project 56 | [Dd]ebugPS/ 57 | [Rr]eleasePS/ 58 | dlldata.c 59 | 60 | # Benchmark Results 61 | BenchmarkDotNet.Artifacts/ 62 | 63 | # .NET Core 64 | project.lock.json 65 | project.fragment.lock.json 66 | artifacts/ 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Visual Studio code coverage results 145 | *.coverage 146 | *.coveragexml 147 | 148 | # NCrunch 149 | _NCrunch_* 150 | .*crunch*.local.xml 151 | nCrunchTemp_* 152 | 153 | # MightyMoose 154 | *.mm.* 155 | AutoTest.Net/ 156 | 157 | # Web workbench (sass) 158 | .sass-cache/ 159 | 160 | ChimeHelperSetup.exe 161 | 162 | # Installshield output folder 163 | [Ee]xpress/ 164 | 165 | # DocProject is a documentation generator add-in 166 | DocProject/buildhelp/ 167 | DocProject/Help/*.HxT 168 | DocProject/Help/*.HxC 169 | DocProject/Help/*.hhc 170 | DocProject/Help/*.hhk 171 | DocProject/Help/*.hhp 172 | DocProject/Help/Html2 173 | DocProject/Help/html 174 | 175 | # Click-Once directory 176 | publish/ 177 | 178 | # Publish Web Output 179 | *.[Pp]ublish.xml 180 | *.azurePubxml 181 | # Note: Comment the next line if you want to checkin your web deploy settings, 182 | # but database connection strings (with potential passwords) will be unencrypted 183 | *.pubxml 184 | *.publishproj 185 | 186 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 187 | # checkin your Azure Web App publish settings, but sensitive information contained 188 | # in these scripts will be unencrypted 189 | PublishScripts/ 190 | 191 | # NuGet Packages 192 | *.nupkg 193 | # NuGet Symbol Packages 194 | *.snupkg 195 | # The packages folder can be ignored because of Package Restore 196 | **/[Pp]ackages/* 197 | # except build/, which is used as an MSBuild target. 198 | !**/[Pp]ackages/build/ 199 | # Uncomment if necessary however generally it will be regenerated when needed 200 | #!**/[Pp]ackages/repositories.config 201 | # NuGet v3's project.json files produces more ignorable files 202 | *.nuget.props 203 | *.nuget.targets 204 | 205 | # Microsoft Azure Build Output 206 | csx/ 207 | *.build.csdef 208 | 209 | # Microsoft Azure Emulator 210 | ecf/ 211 | rcf/ 212 | 213 | # Windows Store app package directories and files 214 | AppPackages/ 215 | BundleArtifacts/ 216 | Package.StoreAssociation.xml 217 | _pkginfo.txt 218 | *.appx 219 | *.appxbundle 220 | *.appxupload 221 | 222 | # Visual Studio cache files 223 | # files ending in .cache can be ignored 224 | *.[Cc]ache 225 | # but keep track of directories ending in .cache 226 | !?*.[Cc]ache/ 227 | 228 | # Others 229 | ClientBin/ 230 | ~$* 231 | *~ 232 | *.dbmdl 233 | *.dbproj.schemaview 234 | *.jfm 235 | *.pfx 236 | *.publishsettings 237 | orleans.codegen.cs 238 | 239 | # Including strong name files can present a security risk 240 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 241 | #*.snk 242 | 243 | # Since there are multiple workflows, uncomment next line to ignore bower_components 244 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 245 | #bower_components/ 246 | 247 | # RIA/Silverlight projects 248 | Generated_Code/ 249 | 250 | # Backup & report files from converting an old project file 251 | # to a newer Visual Studio version. Backup files are not needed, 252 | # because we have git ;-) 253 | _UpgradeReport_Files/ 254 | Backup*/ 255 | UpgradeLog*.XML 256 | UpgradeLog*.htm 257 | ServiceFabricBackup/ 258 | *.rptproj.bak 259 | 260 | # SQL Server files 261 | *.mdf 262 | *.ldf 263 | *.ndf 264 | 265 | # Business Intelligence projects 266 | *.rdl.data 267 | *.bim.layout 268 | *.bim_*.settings 269 | *.rptproj.rsuser 270 | *- [Bb]ackup.rdl 271 | *- [Bb]ackup ([0-9]).rdl 272 | *- [Bb]ackup ([0-9][0-9]).rdl 273 | 274 | # Microsoft Fakes 275 | FakesAssemblies/ 276 | 277 | # GhostDoc plugin setting file 278 | *.GhostDoc.xml 279 | 280 | # Node.js Tools for Visual Studio 281 | .ntvs_analysis.dat 282 | node_modules/ 283 | 284 | # Visual Studio 6 build log 285 | *.plg 286 | 287 | # Visual Studio 6 workspace options file 288 | *.opt 289 | 290 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 291 | *.vbw 292 | 293 | # Visual Studio LightSwitch build output 294 | **/*.HTMLClient/GeneratedArtifacts 295 | **/*.DesktopClient/GeneratedArtifacts 296 | **/*.DesktopClient/ModelManifest.xml 297 | **/*.Server/GeneratedArtifacts 298 | **/*.Server/ModelManifest.xml 299 | _Pvt_Extensions 300 | 301 | # Paket dependency manager 302 | .paket/paket.exe 303 | paket-files/ 304 | 305 | # FAKE - F# Make 306 | .fake/ 307 | 308 | # CodeRush personal settings 309 | .cr/personal 310 | 311 | # Python Tools for Visual Studio (PTVS) 312 | __pycache__/ 313 | *.pyc 314 | 315 | # Cake - Uncomment if you are using it 316 | # tools/** 317 | # !tools/packages.config 318 | 319 | # Tabs Studio 320 | *.tss 321 | 322 | # Telerik's JustMock configuration file 323 | *.jmconfig 324 | 325 | # BizTalk build output 326 | *.btp.cs 327 | *.btm.cs 328 | *.odx.cs 329 | *.xsd.cs 330 | 331 | # OpenCover UI analysis results 332 | OpenCover/ 333 | 334 | # Azure Stream Analytics local run output 335 | ASALocalRun/ 336 | 337 | # MSBuild Binary and Structured Log 338 | *.binlog 339 | 340 | # NVidia Nsight GPU debugger configuration file 341 | *.nvuser 342 | 343 | # MFractors (Xamarin productivity tool) working folder 344 | .mfractor/ 345 | 346 | # Local History for Visual Studio 347 | .localhistory/ 348 | 349 | # BeatPulse healthcheck temp database 350 | healthchecksdb 351 | 352 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 353 | MigrationBackup/ 354 | 355 | # Ionide (cross platform F# VS Code tools) working folder 356 | .ionide/ -------------------------------------------------------------------------------- /Toastify/Settings.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Input; 6 | using System.Windows.Forms; 7 | using System.Globalization; 8 | 9 | namespace Toastify 10 | { 11 | /// 12 | /// Logique d'interaction pour Window1.xaml 13 | /// 14 | public partial class Settings : Window 15 | { 16 | public SettingsXml settings; 17 | private readonly Toast toast; 18 | 19 | private readonly List modifierKeys = new List { System.Windows.Input.Key.LeftCtrl, System.Windows.Input.Key.RightCtrl, System.Windows.Input.Key.LeftAlt, System.Windows.Input.Key.Right, System.Windows.Input.Key.LeftShift, System.Windows.Input.Key.RightShift, System.Windows.Input.Key.LWin, System.Windows.Input.Key.RWin, System.Windows.Input.Key.System }; 20 | 21 | private static Settings _current; 22 | 23 | public static void Launch(Toast toast) 24 | { 25 | if (_current != null) 26 | { 27 | _current.Activate(); 28 | } 29 | else 30 | { 31 | new Settings(toast).ShowDialog(); 32 | } 33 | } 34 | 35 | private Settings(Toast toast) 36 | { 37 | Telemetry.TrackEvent(TelemetryCategory.General, Telemetry.TelemetryEvent.SettingsLaunched); 38 | 39 | this.settings = SettingsXml.Current.Clone(); 40 | this.toast = toast; 41 | 42 | InitializeComponent(); 43 | 44 | //Data context initialisation 45 | GeneralGrid.DataContext = this.settings; 46 | 47 | //Slider initialisation 48 | try 49 | { 50 | slTopColor.Value = byte.Parse(settings.ToastColorTop.Substring(1, 2), NumberStyles.AllowHexSpecifier); 51 | slBottomColor.Value = byte.Parse(settings.ToastColorBottom.Substring(1, 2), NumberStyles.AllowHexSpecifier); 52 | slBorderColor.Value = byte.Parse(settings.ToastBorderColor.Substring(1, 2), NumberStyles.AllowHexSpecifier); 53 | } 54 | catch { } 55 | 56 | if (_current == null) 57 | _current = this; 58 | } 59 | 60 | private void Window_Closed(object sender, EventArgs e) 61 | { 62 | if (_current == this) 63 | _current = null; 64 | } 65 | 66 | //Change Color button click events 67 | private void bChangeColorTop_Click(object sender, RoutedEventArgs e) 68 | { 69 | ColorDialog MyDialog = new ColorDialog(); 70 | string alpha = settings.ToastColorTop.Substring(1, 2); 71 | MyDialog.Color = HexToColor(settings.ToastColorTop); 72 | MyDialog.ShowDialog(); 73 | settings.ToastColorTop = "#" + alpha + MyDialog.Color.R.ToString("X2") + MyDialog.Color.G.ToString("X2") + MyDialog.Color.B.ToString("X2"); 74 | } 75 | 76 | private void bChangeColorBottom_Click(object sender, RoutedEventArgs e) 77 | { 78 | ColorDialog MyDialog = new ColorDialog(); 79 | string alpha = settings.ToastColorBottom.Substring(1, 2); 80 | MyDialog.Color = HexToColor(settings.ToastColorBottom); 81 | MyDialog.ShowDialog(); 82 | settings.ToastColorBottom = "#" + alpha + MyDialog.Color.R.ToString("X2") + MyDialog.Color.G.ToString("X2") + MyDialog.Color.B.ToString("X2"); 83 | } 84 | 85 | private void bChangeBorderColor_Click(object sender, RoutedEventArgs e) 86 | { 87 | ColorDialog MyDialog = new ColorDialog(); 88 | string alpha = settings.ToastBorderColor.Substring(1, 2); 89 | MyDialog.Color = HexToColor(settings.ToastBorderColor); 90 | MyDialog.ShowDialog(); 91 | settings.ToastBorderColor = "#" + alpha + MyDialog.Color.R.ToString("X2") + MyDialog.Color.G.ToString("X2") + MyDialog.Color.B.ToString("X2"); 92 | } 93 | 94 | //Default and Save blick events 95 | private void bDefault_Click(object sender, RoutedEventArgs e) 96 | { 97 | settings.Default(); 98 | SaveAndApplySettings(); 99 | } 100 | 101 | private void bSave_Click(object sender, RoutedEventArgs e) 102 | { 103 | SaveAndApplySettings(); 104 | } 105 | 106 | private void SaveAndApplySettings() 107 | { 108 | settings.Save(replaceCurrent: true); 109 | 110 | toast.InitToast(); 111 | toast.DisplayAction(SpotifyAction.SettingsSaved, null); 112 | } 113 | 114 | //Text box Mouse Wheel events 115 | private void tbCornerTopLeft_MouseWheel(object sender, MouseWheelEventArgs e) 116 | { 117 | if (e.Delta > 0) 118 | { 119 | settings.ToastBorderCornerRadiusTopLeft += 0.1; 120 | } 121 | else if (settings.ToastBorderCornerRadiusTopLeft >= 0.1) 122 | settings.ToastBorderCornerRadiusTopLeft -= 0.1; 123 | } 124 | 125 | private void tbCornerTopRight_MouseWheel(object sender, MouseWheelEventArgs e) 126 | { 127 | if (e.Delta > 0) 128 | settings.ToastBorderCornerRadiusTopRight += 0.1; 129 | else if (settings.ToastBorderCornerRadiusTopLeft >= 0.1) 130 | settings.ToastBorderCornerRadiusTopRight -= 0.1; 131 | } 132 | 133 | private void tbCornerBottomRight_MouseWheel(object sender, MouseWheelEventArgs e) 134 | { 135 | if (e.Delta > 0) 136 | settings.ToastBorderCornerRadiusBottomRight += 0.1; 137 | else if (settings.ToastBorderCornerRadiusBottomRight >= 0.1) 138 | settings.ToastBorderCornerRadiusBottomRight -= 0.1; 139 | } 140 | 141 | private void tbCornerBottomLeft_MouseWheel(object sender, MouseWheelEventArgs e) 142 | { 143 | if (e.Delta > 0) 144 | settings.ToastBorderCornerRadiusBottomLeft += 0.1; 145 | else if (settings.ToastBorderCornerRadiusBottomLeft >= 0.1) 146 | settings.ToastBorderCornerRadiusBottomLeft -= 0.1; 147 | } 148 | 149 | private void tbFadeOutTime_MouseWheel(object sender, MouseWheelEventArgs e) 150 | { 151 | if (e.Delta > 0) 152 | settings.FadeOutTime += 10; 153 | else if (settings.FadeOutTime >= 10) 154 | settings.FadeOutTime -= 10; 155 | } 156 | 157 | private void tbBorderThickness_MouseWheel(object sender, MouseWheelEventArgs e) 158 | { 159 | if (e.Delta > 0) 160 | settings.ToastBorderThickness++; 161 | else if (settings.ToastBorderThickness >= 1) 162 | settings.ToastBorderThickness--; 163 | } 164 | 165 | private void tbToastWidth_MouseWheel(object sender, MouseWheelEventArgs e) 166 | { 167 | if (e.Delta > 0) 168 | settings.ToastWidth += 5; 169 | else if (settings.ToastWidth >= 205) 170 | settings.ToastWidth -= 5; 171 | } 172 | 173 | private void tbToastHeight_MouseWheel(object sender, MouseWheelEventArgs e) 174 | { 175 | if (e.Delta > 0) 176 | settings.ToastHeight += 5; 177 | else if (settings.ToastHeight >= 70) 178 | settings.ToastHeight -= 5; 179 | } 180 | 181 | private void tbPositionLeft_MouseWheel(object sender, MouseWheelEventArgs e) 182 | { 183 | if (e.Delta > 0) 184 | settings.PositionLeft++; 185 | else if (settings.PositionLeft > 0) 186 | settings.PositionLeft--; 187 | } 188 | 189 | private void tbPositionTop_MouseWheel(object sender, MouseWheelEventArgs e) 190 | { 191 | if (e.Delta > 0) 192 | settings.PositionTop++; 193 | else if (settings.PositionTop > 0) 194 | settings.PositionTop--; 195 | } 196 | 197 | //Slider value changed events 198 | private void slTopColor_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) 199 | { 200 | string transparency = Convert.ToByte(slTopColor.Value).ToString("X2"); 201 | settings.ToastColorTop = "#" + transparency + settings.ToastColorTop.Substring(3); 202 | } 203 | 204 | private void slBottomColor_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) 205 | { 206 | string transparency = Convert.ToByte(slBottomColor.Value).ToString("X2"); 207 | settings.ToastColorBottom = "#" + transparency + settings.ToastColorBottom.Substring(3); 208 | } 209 | 210 | private void slBorderColor_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) 211 | { 212 | string transparency = Convert.ToByte(slBorderColor.Value).ToString("X2"); 213 | settings.ToastBorderColor = "#" + transparency + settings.ToastBorderColor.Substring(3); 214 | } 215 | 216 | // Hexadecimal to Color converter 217 | public static System.Drawing.Color HexToColor(string hexColor) 218 | { 219 | //Remove # if present 220 | if (hexColor.IndexOf('#') != -1) 221 | hexColor = hexColor.Replace("#", ""); 222 | 223 | byte alpha = 0; 224 | byte red = 0; 225 | byte green = 0; 226 | byte blue = 0; 227 | 228 | if (hexColor.Length == 8) 229 | { 230 | //#RRGGBB 231 | alpha = byte.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier); 232 | red = byte.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier); 233 | green = byte.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier); 234 | blue = byte.Parse(hexColor.Substring(6, 2), NumberStyles.AllowHexSpecifier); 235 | } 236 | 237 | return System.Drawing.Color.FromArgb(alpha, red, green, blue); 238 | } 239 | 240 | private void txtSingleKey_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e) 241 | { 242 | e.Handled = true; 243 | 244 | var key = e.Key; 245 | 246 | if (key == Key.System) 247 | { 248 | key = e.SystemKey; 249 | } 250 | 251 | txtSingleKey.Text = key.ToString(); 252 | 253 | Hotkey hotkey = lstHotKeys.SelectedItem as Hotkey; 254 | 255 | if (hotkey != null) 256 | { 257 | hotkey.Key = key; 258 | } 259 | } 260 | 261 | private void lstHotKeys_SelectionChanged(object sender, SelectionChangedEventArgs e) 262 | { 263 | Hotkey hotkey = lstHotKeys.SelectedItem as Hotkey; 264 | 265 | if (hotkey != null) 266 | { 267 | txtSingleKey.Text = hotkey.Key.ToString(); 268 | } 269 | } 270 | 271 | private void btnSaveTrackToFilePath_Click(object sender, RoutedEventArgs e) 272 | { 273 | var dialog = new OpenFileDialog(); 274 | 275 | if (SettingsXml.Current.SaveTrackToFilePath != null) 276 | { 277 | dialog.FileName = SettingsXml.Current.SaveTrackToFilePath; 278 | } 279 | 280 | dialog.CheckPathExists = true; 281 | dialog.CheckFileExists = false; 282 | dialog.ShowReadOnly = false; 283 | 284 | if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) 285 | { 286 | settings.SaveTrackToFilePath = dialog.FileName; 287 | } 288 | } 289 | 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /Toastify/HotKey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.ComponentModel; 6 | using System.Windows.Forms; 7 | using System.Windows.Input; 8 | using ManagedWinapi; 9 | using System.Xml.Serialization; 10 | 11 | namespace Toastify 12 | { 13 | public class Hotkey : INotifyPropertyChanged 14 | { 15 | private bool _enabled; 16 | /// 17 | /// Specifies whether or not the hotkey is enabled or disabled from a user's 18 | /// perspective. Does not actually enable or disable the hotkey, use Activate() 19 | /// and Deactivate(). 20 | /// 21 | /// Why do we have these two schemes? We need a way to be able to deactivate a 22 | /// Hotkey (for example when unloading settings) without changing the Enabled 23 | /// property (which only indicates the user's preference) 24 | /// 25 | public bool Enabled 26 | { 27 | get { return _enabled; } 28 | set 29 | { 30 | if (_enabled != value) 31 | { 32 | _enabled = value; 33 | 34 | NotifyPropertyChanged("Enabled"); 35 | } 36 | } 37 | } 38 | 39 | private bool _windowsKey; 40 | public bool WindowsKey 41 | { 42 | get { return _windowsKey; } 43 | set 44 | { 45 | if (_windowsKey != value) 46 | { 47 | _windowsKey = value; 48 | 49 | NotifyPropertyChanged("WindowsKey"); 50 | 51 | CheckIfValid(); 52 | } 53 | } 54 | } 55 | 56 | 57 | private bool _ctrl; 58 | public bool Ctrl 59 | { 60 | get { return _ctrl; } 61 | set 62 | { 63 | if (_ctrl != value) 64 | { 65 | _ctrl = value; 66 | 67 | NotifyPropertyChanged("Notify"); 68 | 69 | CheckIfValid(); 70 | } 71 | } 72 | } 73 | 74 | private bool _alt; 75 | public bool Alt 76 | { 77 | get { return _alt; } 78 | set 79 | { 80 | if (_alt != value) 81 | { 82 | _alt = value; 83 | 84 | NotifyPropertyChanged("Alt"); 85 | 86 | CheckIfValid(); 87 | } 88 | } 89 | } 90 | 91 | private bool _shift; 92 | public bool Shift 93 | { 94 | get { return _shift; } 95 | set 96 | { 97 | if (_shift != value) 98 | { 99 | _shift = value; 100 | 101 | NotifyPropertyChanged("Shift"); 102 | 103 | CheckIfValid(); 104 | } 105 | } 106 | } 107 | 108 | 109 | private Key _key; 110 | public Key Key 111 | { 112 | get { return _key; } 113 | set 114 | { 115 | if (_key != value) 116 | { 117 | _key = value; 118 | 119 | NotifyPropertyChanged("Key"); 120 | 121 | CheckIfValid(); 122 | } 123 | } 124 | } 125 | 126 | private SpotifyAction _action; 127 | public SpotifyAction Action 128 | { 129 | get { return _action; } 130 | set 131 | { 132 | if (_action != value) 133 | { 134 | _action = value; 135 | 136 | NotifyPropertyChanged("Action"); 137 | } 138 | } 139 | } 140 | 141 | [XmlIgnore] 142 | public string HumanReadableAction 143 | { 144 | get 145 | { 146 | switch (Action) 147 | { 148 | case SpotifyAction.CopyTrackInfo: 149 | return "Copy Track Name"; 150 | 151 | case SpotifyAction.PasteTrackInfo: 152 | return "Paste Track Name"; 153 | 154 | case SpotifyAction.Mute: 155 | return "Mute"; 156 | 157 | case SpotifyAction.NextTrack: 158 | return "Next Track"; 159 | 160 | case SpotifyAction.None: 161 | return "None"; 162 | 163 | case SpotifyAction.PlayPause: 164 | return "Play / Pause"; 165 | 166 | case SpotifyAction.PreviousTrack: 167 | return "Previous Track"; 168 | 169 | case SpotifyAction.SettingsSaved: 170 | return "Settings Saved"; 171 | 172 | case SpotifyAction.ShowSpotify: 173 | return "Show / Hide Spotify"; 174 | 175 | case SpotifyAction.ShowToast: 176 | return "Show Toast"; 177 | 178 | case SpotifyAction.Stop: 179 | return "Stop"; 180 | 181 | case SpotifyAction.VolumeDown: 182 | return "Volume Down"; 183 | 184 | case SpotifyAction.VolumeUp: 185 | return "Volume Up"; 186 | 187 | case SpotifyAction.FastForward: 188 | return "Fast Forward"; 189 | 190 | case SpotifyAction.Rewind: 191 | return "Rewind"; 192 | 193 | case SpotifyAction.ThumbsUp: 194 | return "Thumbs Up"; 195 | 196 | case SpotifyAction.ThumbsDown: 197 | return "Thunbs Down"; 198 | } 199 | 200 | return "No Action"; 201 | } 202 | } 203 | 204 | private bool _isValid; 205 | [XmlIgnore] 206 | public bool IsValid 207 | { 208 | get { return _isValid; } 209 | set 210 | { 211 | if (_isValid != value) 212 | { 213 | _isValid = value; 214 | 215 | NotifyPropertyChanged("IsValid"); 216 | } 217 | } 218 | 219 | } 220 | 221 | private string _invalidReason; 222 | [XmlIgnore] 223 | public string InvalidReason 224 | { 225 | get { return _invalidReason; } 226 | set 227 | { 228 | if (_invalidReason != value) 229 | { 230 | _invalidReason = value; 231 | 232 | NotifyPropertyChanged("InvalidReason"); 233 | } 234 | } 235 | } 236 | 237 | private bool _active = false; 238 | private ManagedWinapi.Hotkey _globalKey; 239 | 240 | public Hotkey Clone() 241 | { 242 | Hotkey clone = MemberwiseClone() as Hotkey; 243 | 244 | // regardless of whether or not the original hotkey was active 245 | // the cloned one should not start in an active state 246 | clone._active = false; 247 | 248 | return clone; 249 | } 250 | 251 | /// 252 | /// Turn this HotKey off 253 | /// 254 | public void Deactivate() 255 | { 256 | SetActive(false); 257 | } 258 | 259 | /// 260 | /// Turn this hotkey on. Does nothing if this Hotkey is not enabled 261 | /// 262 | public void Activate() 263 | { 264 | SetActive(true); 265 | } 266 | 267 | private void SetActive(bool value) 268 | { 269 | if (_active != value) 270 | { 271 | _active = value; 272 | 273 | InitGlobalKey(); 274 | } 275 | } 276 | 277 | private void InitGlobalKey() 278 | { 279 | // If we're not enabled shut everything done asap 280 | if (!Enabled || !_active) 281 | { 282 | if (_globalKey != null) 283 | { 284 | _globalKey.Enabled = false; 285 | _globalKey = null; // may as well collect the memory 286 | } 287 | 288 | // may not be false if !Enabled 289 | _active = false; 290 | 291 | return; 292 | } 293 | 294 | if (_globalKey == null) 295 | _globalKey = new ManagedWinapi.Hotkey(); 296 | 297 | // make sure that we don't try to reregister the key midway updating 298 | // the combination 299 | if (_globalKey.Enabled) 300 | _globalKey.Enabled = false; 301 | 302 | _globalKey.WindowsKey = this.WindowsKey; 303 | _globalKey.Alt = this.Alt; 304 | _globalKey.Ctrl = this.Ctrl; 305 | _globalKey.Shift = this.Shift; 306 | _globalKey.KeyCode = ConvertInputKeyToFormsKeys(this.Key); 307 | 308 | _globalKey.HotkeyPressed += (s, e) => { Toast.ActionHookCallback(this); }; 309 | 310 | try 311 | { 312 | _globalKey.Enabled = true; 313 | } 314 | catch (HotkeyAlreadyInUseException) 315 | { 316 | IsValid = false; 317 | InvalidReason = "Hotkey is already in use by a different program"; 318 | } 319 | } 320 | 321 | /// 322 | /// Validity rules are: 323 | /// 324 | /// 1. Ctrl or Alt must be selected 325 | /// 2. a key must be specified 326 | /// 327 | private void CheckIfValid() 328 | { 329 | if (Key == Key.None) 330 | { 331 | IsValid = false; 332 | InvalidReason = "You must select a valid key for your hotkey combination"; 333 | 334 | return; 335 | } 336 | 337 | IsValid = true; 338 | InvalidReason = ""; 339 | } 340 | 341 | #region Static Functions 342 | 343 | private static readonly List _hotkeys = new List(); 344 | 345 | public static void ClearAll() 346 | { 347 | // disable will be called by the destructors, but we want to force a disable 348 | // now so that we don't wait for the GC to clean up the objects 349 | foreach (Hotkey hotkey in _hotkeys) 350 | { 351 | hotkey.Deactivate(); 352 | } 353 | 354 | _hotkeys.Clear(); 355 | } 356 | 357 | private static System.Windows.Forms.Keys ConvertInputKeyToFormsKeys(System.Windows.Input.Key key) 358 | { 359 | if (Enum.GetNames(typeof(System.Windows.Forms.Keys)).Contains(key.ToString())) 360 | return (System.Windows.Forms.Keys)Enum.Parse(typeof(System.Windows.Forms.Keys), key.ToString()); 361 | else 362 | return Keys.None; 363 | } 364 | 365 | #endregion 366 | 367 | private readonly ManagedWinapi.Hotkey key = new ManagedWinapi.Hotkey(); 368 | 369 | public Hotkey() 370 | { 371 | _hotkeys.Add(this); 372 | } 373 | 374 | ~Hotkey() 375 | { 376 | if (key != null) 377 | key.Enabled = false; 378 | } 379 | 380 | [XmlIgnore] 381 | public string HumanReadableKey 382 | { 383 | get 384 | { 385 | StringBuilder sb = new StringBuilder(); 386 | if (this.WindowsKey) sb.Append("Win+"); 387 | if (this.Ctrl) sb.Append("Ctrl+"); 388 | if (this.Alt) sb.Append("Alt+"); 389 | if (this.Shift) sb.Append("Shift+"); 390 | sb.Append(this.Key.ToString()); 391 | return sb.ToString(); 392 | } 393 | } 394 | 395 | #region INotifyPropertyChanged 396 | 397 | public event PropertyChangedEventHandler PropertyChanged; 398 | 399 | private void NotifyPropertyChanged(String info) 400 | { 401 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(info)); 402 | } 403 | 404 | #endregion 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /Toastify/Toastify.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | Debug 6 | AnyCPU 7 | 9.0.30729 8 | 2.0 9 | {CCC4A761-56D2-4188-807F-F7A547DFB031} 10 | WinExe 11 | Properties 12 | Toastify 13 | Toastify 14 | v4.6.1 15 | 512 16 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 4 18 | spotify.ico 19 | Toastify.EntryPoint 20 | 21 | 22 | 3.5 23 | 24 | true 25 | 26 | 27 | SAK 28 | SAK 29 | SAK 30 | SAK 31 | publish\ 32 | true 33 | Web 34 | true 35 | Foreground 36 | 7 37 | Days 38 | false 39 | false 40 | true 41 | http://toastify.codeplex.com/ 42 | true 43 | publish.htm 44 | 1 45 | 1.0.0.%2a 46 | false 47 | true 48 | true 49 | 50 | 51 | true 52 | full 53 | false 54 | bin\Debug\ 55 | DEBUG;TRACE 56 | prompt 57 | 4 58 | false 59 | AllRules.ruleset 60 | false 61 | 62 | 63 | pdbonly 64 | true 65 | ..\InstallationScript\ 66 | TRACE 67 | prompt 68 | 4 69 | AllRules.ruleset 70 | false 71 | 72 | 73 | D38DAF47B3CBFC190231922089F056B55D662088 74 | 75 | 76 | Toastify_TemporaryKey.pfx 77 | 78 | 79 | true 80 | 81 | 82 | false 83 | 84 | 85 | 86 | ..\packages\Garlic.2.1.0.0\lib\net35\Garlic.dll 87 | 88 | 89 | .\ManagedWinapi.dll 90 | 91 | 92 | 93 | ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll 94 | 95 | 96 | ..\packages\SpotifyAPI.Web.6.0.0-beta.9\lib\netstandard2.0\SpotifyAPI.Web.dll 97 | 98 | 99 | 100 | 3.5 101 | 102 | 103 | 104 | 105 | 106 | 107 | 3.5 108 | 109 | 110 | 3.5 111 | 112 | 113 | 114 | 115 | 3.0 116 | 117 | 118 | 3.0 119 | 120 | 121 | 3.0 122 | 123 | 124 | 3.0 125 | 126 | 127 | 128 | 129 | MSBuild:Compile 130 | Designer 131 | MSBuild:Compile 132 | Designer 133 | 134 | 135 | Designer 136 | MSBuild:Compile 137 | MSBuild:Compile 138 | Designer 139 | 140 | 141 | Designer 142 | MSBuild:Compile 143 | 144 | 145 | MSBuild:Compile 146 | Designer 147 | MSBuild:Compile 148 | Designer 149 | 150 | 151 | App.xaml 152 | Code 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | Settings.xaml 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | Toast.xaml 169 | Code 170 | 171 | 172 | 173 | 174 | About.xaml 175 | 176 | 177 | 178 | 179 | 180 | Code 181 | 182 | 183 | True 184 | True 185 | Resources.resx 186 | 187 | 188 | True 189 | Settings.settings 190 | True 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | ResXFileCodeGenerator 199 | Resources.Designer.cs 200 | 201 | 202 | 203 | Always 204 | 205 | 206 | 207 | SettingsSingleFileGenerator 208 | Settings.Designer.cs 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | {2869dbfe-7762-4930-95ea-2b0c111cf353} 227 | AutoHotkey.Interop 228 | 229 | 230 | {4f92be1f-a5cc-4604-9185-1f09ddafc7b8} 231 | Toastify.Plugin 232 | 233 | 234 | 235 | 236 | False 237 | .NET Framework 3.5 SP1 Client Profile 238 | false 239 | 240 | 241 | False 242 | .NET Framework 3.5 SP1 243 | true 244 | 245 | 246 | False 247 | Windows Installer 3.1 248 | true 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | Always 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 279 | -------------------------------------------------------------------------------- /Toastify/VolumeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Toastify 6 | { 7 | class VolumeHelper 8 | { 9 | // base code from: http://stackoverflow.com/a/14322736 10 | 11 | public static void IncrementVolume(string name) 12 | { 13 | var curVolume = GetApplicationVolume(name); 14 | 15 | if (curVolume != null && curVolume < 100) 16 | SetApplicationVolume(name, (float)curVolume + 2); 17 | } 18 | 19 | public static void DecrementVolume(string name) 20 | { 21 | var curVolume = GetApplicationVolume(name); 22 | 23 | if (curVolume != null && curVolume > 0) 24 | SetApplicationVolume(name, (float)curVolume - 2); 25 | } 26 | 27 | public static float? GetApplicationVolume(string name) 28 | { 29 | ISimpleAudioVolume volume = GetVolumeObject(name); 30 | if (volume == null) 31 | return null; 32 | 33 | volume.GetMasterVolume(out float level); 34 | return level * 100; 35 | } 36 | 37 | public static bool? GetApplicationMute(string name) 38 | { 39 | ISimpleAudioVolume volume = GetVolumeObject(name); 40 | if (volume == null) 41 | return null; 42 | 43 | volume.GetMute(out bool mute); 44 | return mute; 45 | } 46 | 47 | public static void SetApplicationVolume(string name, float level) 48 | { 49 | ISimpleAudioVolume volume = GetVolumeObject(name); 50 | if (volume == null) 51 | return; 52 | 53 | Guid guid = Guid.Empty; 54 | volume.SetMasterVolume(level / 100, ref guid); 55 | } 56 | 57 | internal static void ToggleApplicationMute(string name) 58 | { 59 | 60 | var muted = GetApplicationMute(name); 61 | 62 | if (muted == null) 63 | return; 64 | 65 | SetApplicationMute(name, !(bool)muted); 66 | } 67 | 68 | 69 | public static void SetApplicationMute(string name, bool mute) 70 | { 71 | ISimpleAudioVolume volume = GetVolumeObject(name); 72 | if (volume == null) 73 | return; 74 | 75 | Guid guid = Guid.Empty; 76 | volume.SetMute(mute, ref guid); 77 | } 78 | 79 | public static IEnumerable EnumerateApplications() 80 | { 81 | // get the speakers (1st render + multimedia) device 82 | IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); 83 | deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out IMMDevice speakers); 84 | 85 | // activate the session manager. we need the enumerator 86 | Guid IID_IAudioSessionManager2 = typeof(IAudioSessionManager2).GUID; 87 | speakers.Activate(ref IID_IAudioSessionManager2, 0, IntPtr.Zero, out object o); 88 | IAudioSessionManager2 mgr = (IAudioSessionManager2)o; 89 | 90 | // enumerate sessions for on this device 91 | mgr.GetSessionEnumerator(out IAudioSessionEnumerator sessionEnumerator); 92 | sessionEnumerator.GetCount(out int count); 93 | 94 | for (int i = 0; i < count; i++) 95 | { 96 | IAudioSessionControl2 ctl2; 97 | 98 | sessionEnumerator.GetSession(i, out IAudioSessionControl ctl); 99 | 100 | ctl2 = ctl as IAudioSessionControl2; 101 | 102 | if (ctl2 != null) 103 | { 104 | 105 | ctl2.GetSessionIdentifier(out string sout1); 106 | ctl2.GetProcessId(out uint pid); 107 | ctl2.GetSessionInstanceIdentifier(out string sout2); 108 | 109 | } 110 | 111 | ctl.GetDisplayName(out string dn); 112 | yield return dn; 113 | Marshal.ReleaseComObject(ctl); 114 | } 115 | Marshal.ReleaseComObject(sessionEnumerator); 116 | Marshal.ReleaseComObject(mgr); 117 | Marshal.ReleaseComObject(speakers); 118 | Marshal.ReleaseComObject(deviceEnumerator); 119 | } 120 | 121 | private static ISimpleAudioVolume GetVolumeObject(string name) 122 | { 123 | // get the speakers (1st render + multimedia) device 124 | IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator()); 125 | deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out IMMDevice speakers); 126 | 127 | // activate the session manager. we need the enumerator 128 | Guid IID_IAudioSessionManager2 = typeof(IAudioSessionManager2).GUID; 129 | speakers.Activate(ref IID_IAudioSessionManager2, 0, IntPtr.Zero, out object o); 130 | IAudioSessionManager2 mgr = (IAudioSessionManager2)o; 131 | 132 | // enumerate sessions for on this device 133 | mgr.GetSessionEnumerator(out IAudioSessionEnumerator sessionEnumerator); 134 | sessionEnumerator.GetCount(out int count); 135 | 136 | // lower case name for easier comparison with the Session ID later on 137 | name = name.ToLower(); 138 | 139 | // search for an audio session with the required name 140 | // Note: Since GetDisplayName only returns a real name if the application bothered to call SetDisplayName 141 | // (which apps like Spotify do not), we instead use the SessionID (which usually includes the exe name) 142 | ISimpleAudioVolume volumeControl = null; 143 | 144 | for (int i = 0; i < count; i++) 145 | { 146 | 147 | IAudioSessionControl2 ctl2; 148 | 149 | sessionEnumerator.GetSession(i, out IAudioSessionControl ctl); 150 | 151 | ctl2 = ctl as IAudioSessionControl2; 152 | 153 | ctl.GetDisplayName(out string dn); 154 | 155 | if (ctl2 != null) 156 | { 157 | 158 | ctl2.GetSessionIdentifier(out string sessionID); 159 | 160 | if (sessionID.ToLower().Contains(name)) 161 | { 162 | volumeControl = ctl as ISimpleAudioVolume; 163 | break; 164 | } 165 | } 166 | 167 | if (ctl != null) 168 | Marshal.ReleaseComObject(ctl); 169 | 170 | if (ctl2 != null) 171 | Marshal.ReleaseComObject(ctl2); 172 | } 173 | 174 | Marshal.ReleaseComObject(sessionEnumerator); 175 | Marshal.ReleaseComObject(mgr); 176 | Marshal.ReleaseComObject(speakers); 177 | Marshal.ReleaseComObject(deviceEnumerator); 178 | 179 | return volumeControl; 180 | } 181 | 182 | // 183 | // Should probably be in the central WinHelper (or a NativeMethods) but it seemed cleaner to 184 | // keep it all together. 185 | // 186 | 187 | [ComImport] 188 | [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] 189 | internal class MMDeviceEnumerator 190 | { 191 | } 192 | 193 | internal enum EDataFlow 194 | { 195 | eRender, 196 | eCapture, 197 | eAll, 198 | EDataFlow_enum_count 199 | } 200 | 201 | internal enum ERole 202 | { 203 | eConsole, 204 | eMultimedia, 205 | eCommunications, 206 | ERole_enum_count 207 | } 208 | 209 | [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 210 | internal interface IMMDeviceEnumerator 211 | { 212 | int NotImpl1(); 213 | 214 | [PreserveSig] 215 | int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice); 216 | 217 | // the rest is not implemented 218 | } 219 | 220 | [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 221 | internal interface IMMDevice 222 | { 223 | [PreserveSig] 224 | int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface); 225 | 226 | // the rest is not implemented 227 | } 228 | 229 | [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 230 | internal interface IAudioSessionManager2 231 | { 232 | int NotImpl1(); 233 | int NotImpl2(); 234 | 235 | [PreserveSig] 236 | int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum); 237 | 238 | // the rest is not implemented 239 | } 240 | 241 | [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 242 | internal interface IAudioSessionEnumerator 243 | { 244 | [PreserveSig] 245 | int GetCount(out int SessionCount); 246 | 247 | [PreserveSig] 248 | int GetSession(int SessionCount, out IAudioSessionControl Session); 249 | } 250 | 251 | [Guid("F4B1A599-7266-4319-A8CA-E70ACB11E8CD"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 252 | internal interface IAudioSessionControl 253 | { 254 | int NotImpl1(); 255 | 256 | [PreserveSig] 257 | int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); 258 | 259 | // the rest is not implemented 260 | } 261 | 262 | public enum AudioSessionState 263 | { 264 | AudioSessionStateInactive = 0, 265 | AudioSessionStateActive = 1, 266 | AudioSessionStateExpired = 2 267 | } 268 | 269 | public enum AudioSessionDisconnectReason 270 | { 271 | DisconnectReasonDeviceRemoval = 0, 272 | DisconnectReasonServerShutdown = (DisconnectReasonDeviceRemoval + 1), 273 | DisconnectReasonFormatChanged = (DisconnectReasonServerShutdown + 1), 274 | DisconnectReasonSessionLogoff = (DisconnectReasonFormatChanged + 1), 275 | DisconnectReasonSessionDisconnected = (DisconnectReasonSessionLogoff + 1), 276 | DisconnectReasonExclusiveModeOverride = (DisconnectReasonSessionDisconnected + 1) 277 | } 278 | 279 | [Guid("24918ACC-64B3-37C1-8CA9-74A66E9957A8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 280 | public interface IAudioSessionEvents 281 | { 282 | [PreserveSig] 283 | int OnDisplayNameChanged([MarshalAs(UnmanagedType.LPWStr)] string NewDisplayName, Guid EventContext); 284 | [PreserveSig] 285 | int OnIconPathChanged([MarshalAs(UnmanagedType.LPWStr)] string NewIconPath, Guid EventContext); 286 | [PreserveSig] 287 | int OnSimpleVolumeChanged(float NewVolume, bool newMute, Guid EventContext); 288 | [PreserveSig] 289 | int OnChannelVolumeChanged(UInt32 ChannelCount, IntPtr NewChannelVolumeArray, UInt32 ChangedChannel, Guid EventContext); 290 | [PreserveSig] 291 | int OnGroupingParamChanged(Guid NewGroupingParam, Guid EventContext); 292 | [PreserveSig] 293 | int OnStateChanged(AudioSessionState NewState); 294 | [PreserveSig] 295 | int OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason); 296 | } 297 | 298 | [Guid("BFB7FF88-7239-4FC9-8FA2-07C950BE9C6D"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 299 | public interface IAudioSessionControl2 300 | { 301 | [PreserveSig] 302 | int GetState(out AudioSessionState state); 303 | [PreserveSig] 304 | int GetDisplayName([Out(), MarshalAs(UnmanagedType.LPWStr)] out string name); 305 | [PreserveSig] 306 | int SetDisplayName([MarshalAs(UnmanagedType.LPWStr)] string value, Guid EventContext); 307 | [PreserveSig] 308 | int GetIconPath([Out(), MarshalAs(UnmanagedType.LPWStr)] out string Path); 309 | [PreserveSig] 310 | int SetIconPath([MarshalAs(UnmanagedType.LPWStr)] string Value, Guid EventContext); 311 | [PreserveSig] 312 | int GetGroupingParam(out Guid GroupingParam); 313 | [PreserveSig] 314 | int SetGroupingParam(Guid Override, Guid Eventcontext); 315 | [PreserveSig] 316 | int RegisterAudioSessionNotification(IAudioSessionEvents NewNotifications); 317 | [PreserveSig] 318 | int UnregisterAudioSessionNotification(IAudioSessionEvents NewNotifications); 319 | [PreserveSig] 320 | int GetSessionIdentifier([Out(), MarshalAs(UnmanagedType.LPWStr)] out string retVal); 321 | [PreserveSig] 322 | int GetSessionInstanceIdentifier([Out(), MarshalAs(UnmanagedType.LPWStr)] out string retVal); 323 | [PreserveSig] 324 | int GetProcessId(out UInt32 retvVal); 325 | [PreserveSig] 326 | int IsSystemSoundsSession(); 327 | [PreserveSig] 328 | int SetDuckingPreference(bool optOut); 329 | } 330 | 331 | 332 | [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 333 | internal interface ISimpleAudioVolume 334 | { 335 | [PreserveSig] 336 | int SetMasterVolume(float fLevel, ref Guid EventContext); 337 | 338 | [PreserveSig] 339 | int GetMasterVolume(out float pfLevel); 340 | 341 | [PreserveSig] 342 | int SetMute(bool bMute, ref Guid EventContext); 343 | 344 | [PreserveSig] 345 | int GetMute(out bool pbMute); 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /AutoHotkey.Interop/AutoHotkeyDll.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AutoHotkey.Interop 5 | { 6 | /// 7 | /// These functions serve as a flat wrapper for AutoHotkey.dll. 8 | /// They assume AutoHotkey.dll is in the same directory as your 9 | /// executable. 10 | /// 11 | internal class AutoHotkeyDll 12 | { 13 | private const string DLLPATH = "AutoHotkey.dll"; 14 | 15 | #region Create Thread 16 | 17 | /// 18 | /// Start new thread from ahk file. 19 | /// 20 | /// This parameter must be a path to existing ahk file. 21 | /// Additional parameter passed to AutoHotkey.dll (not available in Version 2 alpha). 22 | /// Parameters passed to dll. 23 | /// ahkdll returns a thread handle. 24 | /// ahktextdll is available in AutoHotkey[Mini].dll only, not in AutoHotkey.exe. 25 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 26 | public static extern uint ahkdll( 27 | [MarshalAs(UnmanagedType.LPWStr)] string Path, 28 | [MarshalAs(UnmanagedType.LPWStr)] string Options, 29 | [MarshalAs(UnmanagedType.LPWStr)] string Parameters); 30 | 31 | /// 32 | /// ahktextdll is used to launch a script in a separate thread from text/variable. 33 | /// 34 | /// This parameter must be a string with ahk script. 35 | /// Additional parameter passed to AutoHotkey.dll (not available in Version 2 alpha). 36 | /// Parameters passed to dll. 37 | /// ahkdll returns a thread handle. 38 | /// ahktextdll is available in AutoHotkey[Mini].dll only, not in AutoHotkey.exe. 39 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 40 | public static extern uint ahktextdll( 41 | [MarshalAs(UnmanagedType.LPWStr)] string Code, 42 | [MarshalAs(UnmanagedType.LPWStr)] string Options, 43 | [MarshalAs(UnmanagedType.LPWStr)] string Parameters); 44 | 45 | #endregion 46 | 47 | #region Determine Thread's State 48 | 49 | /// 50 | /// ahkReady is used to check if a dll script is running or not. 51 | /// 52 | /// 1 if a thread is running or 0 otherwise. 53 | /// Available in AutoHotkey[Mini].dll only, not in AutoHotkey.exe. 54 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 55 | public static extern bool ahkReady(); 56 | 57 | #endregion 58 | 59 | #region Control Thread 60 | 61 | /// 62 | /// ahkTerminate is used to stop and exit a running script. 63 | /// 64 | /// Time in milliseconds to wait until thread exits. 65 | /// Returns always 0. 66 | /// Available in AutoHotkey[Mini].dll only, not in AutoHotkey.exe. 67 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 68 | public static extern void ahkTerminate(uint timeout); 69 | 70 | /// 71 | /// ahkReload is used to terminate and start a running script again. 72 | /// 73 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 74 | public static extern void ahkReload(); 75 | 76 | /// 77 | /// ahkPause will pause/un-pause a thread and run traditional AutoHotkey Sleep internally. 78 | /// 79 | /// Should be "On" or "Off" as a string 80 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 81 | public static extern void ahkPause( 82 | [MarshalAs(UnmanagedType.LPWStr)] string strState); 83 | 84 | #endregion 85 | 86 | #region Add New Code 87 | 88 | /// 89 | /// addFile includes additional script from a file to the running script. 90 | /// 91 | /// Path to a file that will be added to a running script. 92 | /// Allow duplicate includes. 93 | /// Ignore if loading a file failed. 94 | /// addFile returns a pointer to the first line of new created code. 95 | /// pointerLine can be used in ahkExecuteLine to execute one line only or until a return is encountered. 96 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 97 | public static extern uint addFile( 98 | [MarshalAs(UnmanagedType.LPWStr)]string FilePath, 99 | byte AllowDuplicateInclude, 100 | byte IgnoreLoadFailure); 101 | 102 | // Constant values for the execute parameter of addScript 103 | public struct Execute 104 | { 105 | public const byte Add = 0, Run = 1, RunWait = 2; 106 | } 107 | 108 | /// 109 | /// addScript includes additional script from a string to the running script. 110 | /// 111 | /// cript that will be added to a running script. 112 | /// Determines whether the added script should be executed. 113 | /// addScript returns a pointer to the first line of new created code. 114 | /// pointerLine can be used in ahkExecuteLine to execute one line only or until a return is encountered. 115 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 116 | public static extern uint addScript( 117 | [MarshalAs(UnmanagedType.LPWStr)]string code, 118 | byte execute); 119 | 120 | #endregion 121 | 122 | #region Execute Some Code 123 | 124 | /// 125 | /// Execute a script from a string that contains ahk script. 126 | /// 127 | /// Script as string/text or variable containing script that will be executed. 128 | /// Returns true if script was executed and false if there was an error. 129 | /// ahkExec will execute the code and delete it before it returns. 130 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 131 | public static extern bool ahkExec( 132 | [MarshalAs(UnmanagedType.LPWStr)] string code); 133 | 134 | //TODO: ahkExecuteLine 135 | 136 | /// 137 | /// ahkLabel is used to launch a Goto/GoSub routine in script. 138 | /// 139 | /// Name of label to execute. 140 | /// Do not to wait until execution finished. 141 | /// 1 if label exists 0 otherwise. 142 | /// Default is 0 = wait for code to finish execution. 143 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 144 | public static extern bool ahkLabel( 145 | [MarshalAs(UnmanagedType.LPWStr)] string labelName, 146 | bool noWait); 147 | 148 | 149 | /// 150 | /// ahkFunction is used to launch a function in script. 151 | /// 152 | /// Name of function to call. 153 | /// The 1st parameter, or null 154 | /// The 2nd parameter, or null 155 | /// The 3rd parameter, or null 156 | /// The 4th parameter, or null 157 | /// The 5th parameter, or null 158 | /// The 6th parameter, or null 159 | /// The 7th parameter, or null 160 | /// The 8th parameter, or null 161 | /// The 9th parameter, or null 162 | /// The 10th parameter, or null 163 | /// Return value is always a string/text, add 0 to make sure it resolves to digit if necessary. 164 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 165 | public static extern IntPtr ahkFunction( 166 | [MarshalAs(UnmanagedType.LPWStr)] string functionName, 167 | [MarshalAs(UnmanagedType.LPWStr)] string parameter1, 168 | [MarshalAs(UnmanagedType.LPWStr)] string parameter2, 169 | [MarshalAs(UnmanagedType.LPWStr)] string parameter3, 170 | [MarshalAs(UnmanagedType.LPWStr)] string parameter4, 171 | [MarshalAs(UnmanagedType.LPWStr)] string parameter5, 172 | [MarshalAs(UnmanagedType.LPWStr)] string parameter6, 173 | [MarshalAs(UnmanagedType.LPWStr)] string parameter7, 174 | [MarshalAs(UnmanagedType.LPWStr)] string parameter8, 175 | [MarshalAs(UnmanagedType.LPWStr)] string parameter9, 176 | [MarshalAs(UnmanagedType.LPWStr)] string parameter10); 177 | 178 | 179 | /// 180 | /// ahkFunction is used to launch a function in script. 181 | /// 182 | /// Name of function to call. 183 | /// Parameters to pass to function. 184 | /// 0 if function exists else -1. 185 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 186 | public static extern bool ahkPostFunction( 187 | [MarshalAs(UnmanagedType.LPWStr)] string functionName, 188 | [MarshalAs(UnmanagedType.LPWStr)] string Parameters); 189 | 190 | #endregion 191 | 192 | #region Working With Variables 193 | 194 | /// 195 | /// ahkassign is used to assign a string to a variable in script. 196 | /// 197 | /// Name of a variable. 198 | /// Value to assign to variable. 199 | /// Returns value is 0 on success and -1 on failure. 200 | /// ahkassign will create the variable if it does not exist. 201 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 202 | public static extern bool ahkassign( 203 | [MarshalAs(UnmanagedType.LPWStr)] string VariableName, 204 | [MarshalAs(UnmanagedType.LPWStr)] string NewValue); 205 | 206 | /// 207 | /// ahkgetvar is used to get a value from a variable in script. 208 | /// 209 | /// Name of variable to get value from. 210 | /// Get value or pointer. 211 | /// Returned value is always a string, add 0 to convert to integer if necessary, especially when using getPointer. 212 | /// ahkgetvar returns empty string if variable does not exist or is empty. 213 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 214 | public static extern IntPtr ahkgetvar( 215 | [MarshalAs(UnmanagedType.LPWStr)] string VariableName, 216 | [MarshalAs(UnmanagedType.I4)] int GetPointer); 217 | 218 | #endregion 219 | 220 | #region Advanced 221 | 222 | /// 223 | /// ahkFundFunc is used to get function its pointer 224 | /// 225 | /// Name of function to call. 226 | /// Function pointer. 227 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 228 | public static extern IntPtr ahkFindFunc( 229 | [MarshalAs(UnmanagedType.LPWStr)] string FuncName); 230 | 231 | /// 232 | /// ahkFindLabel is used to get a pointer to the label. 233 | /// 234 | /// Name of label. 235 | /// ahkFindLabel returns a pointer to a line where label points to. 236 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] 237 | public static extern IntPtr ahkFindLabel( 238 | [MarshalAs(UnmanagedType.LPWStr)] string LabelName); 239 | 240 | /// 241 | /// Build in function to get a pointer to the structure of a user-defined variable. 242 | /// 243 | /// the name of the variable 244 | /// The pointer to the variable. 245 | [DllImport(DLLPATH, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "getVar")] 246 | public static extern IntPtr GetVar( 247 | [MarshalAs(UnmanagedType.LPWStr)] string Variable); 248 | 249 | #endregion 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /Toastify/Spotify/Spotify.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading; 5 | using System.IO; 6 | using System.Diagnostics; 7 | using SpotifyAPI.Web; 8 | using System.Threading.Tasks; 9 | using System.Text.RegularExpressions; 10 | 11 | namespace Toastify 12 | { 13 | internal static class Spotify 14 | { 15 | /// 16 | /// The number of seconds for which the last GetSpotify() result is immediately returned 17 | /// 18 | private const int _GET_SPOTIFY_RETURN_LAST_SEC = 5; 19 | 20 | private static AutoHotkey.Interop.AutoHotkeyEngine _ahk; 21 | 22 | private static DateTime _lastGetSpotifyCall = DateTime.MinValue; 23 | private static int _cachedProcId; 24 | private static IntPtr _cachedHWnd; 25 | 26 | public static void StartSpotify() 27 | { 28 | if (IsRunning()) 29 | return; 30 | 31 | // spotify installs a protocol handler, "spotify:", use that to launch spotify 32 | 33 | Process.Start("spotify:"); 34 | 35 | if (SettingsXml.Current.MinimizeSpotifyOnStartup) 36 | { 37 | Minimize(); 38 | } 39 | else 40 | { 41 | 42 | // we need to let Spotify start up before interacting with it fully. 2 seconds is a relatively 43 | // safe amount of time to wait, even if the pattern is gross. (Minimize() doesn't need it since 44 | // it waits for the Window to appear before minimizing) 45 | var remainingSleep = 2000; 46 | 47 | while (Spotify.GetSpotify() == IntPtr.Zero && remainingSleep > 0) 48 | { 49 | Thread.Sleep(100); 50 | remainingSleep -= 100; 51 | } 52 | } 53 | } 54 | 55 | private static void Minimize() 56 | { 57 | var remainingSleep = 2000; 58 | 59 | IntPtr hWnd; 60 | 61 | // Since Minimize is often called during startup, the hWnd is often not created yet 62 | // wait a maximum of remainingSleep for it to appear and then minimize it if it did. 63 | while ((hWnd = Spotify.GetSpotify()) == IntPtr.Zero && remainingSleep > 0) 64 | { 65 | Thread.Sleep(100); 66 | remainingSleep -= 100; 67 | } 68 | 69 | if (hWnd != IntPtr.Zero) 70 | { 71 | // disgusting but sadly neccessary. Let Spotify initialize a bit before minimizing it 72 | // otherwise the window hides itself and doesn't respond to taskbar clicks. 73 | // I tried to work around this by waiting for the window size to initialize (via GetWindowRect) 74 | // but that didn't work, there is some internal initialization that needs to occur. 75 | Thread.Sleep(500); 76 | Win32.ShowWindow(hWnd, Win32.Constants.SW_SHOWMINIMIZED); 77 | } 78 | } 79 | 80 | private static void KillProc(string name) 81 | { 82 | // let's play nice and try to gracefully clear out all Sync processes 83 | var procs = System.Diagnostics.Process.GetProcessesByName(name); 84 | 85 | foreach (var proc in procs) 86 | { 87 | // lParam == Band Process Id, passed in below 88 | Win32.EnumWindows(delegate (IntPtr hWnd, IntPtr lParam) 89 | { 90 | Win32.GetWindowThreadProcessId(hWnd, out uint processId); 91 | 92 | // Essentially: Find every hWnd associated with this process and ask it to go away 93 | if (processId == (uint)lParam) 94 | { 95 | Win32.SendMessage(hWnd, Win32.Constants.WM_CLOSE, IntPtr.Zero, IntPtr.Zero); 96 | Win32.SendMessage(hWnd, Win32.Constants.WM_QUIT, IntPtr.Zero, IntPtr.Zero); 97 | } 98 | 99 | return true; 100 | }, 101 | (IntPtr)proc.Id); 102 | } 103 | 104 | // let everything calm down 105 | Thread.Sleep(1000); 106 | 107 | procs = System.Diagnostics.Process.GetProcessesByName(name); 108 | 109 | // ok, no more mister nice guy. Sadly. 110 | foreach (var proc in procs) 111 | { 112 | try 113 | { 114 | proc.Kill(); 115 | } 116 | catch { } // ignore exceptions (usually due to trying to kill non-existant child processes 117 | } 118 | } 119 | 120 | public static void KillSpotify() 121 | { 122 | KillProc("spotify"); 123 | } 124 | 125 | /// 126 | /// Find a running instance of Spotify 127 | /// 128 | /// HWND of the Spotify window 129 | private static IntPtr GetSpotify() 130 | { 131 | if (DateTime.Now.Subtract(_lastGetSpotifyCall).TotalSeconds < _GET_SPOTIFY_RETURN_LAST_SEC) 132 | return _cachedHWnd; 133 | 134 | _lastGetSpotifyCall = DateTime.Now; 135 | var rv = IntPtr.Zero; 136 | 137 | // Spotify have made things a little harder with their use of electron 138 | // In order to not pick up on other Electron windows, first find the process and then the window 139 | var procs = Process.GetProcessesByName("spotify"); 140 | 141 | if (Array.Exists(procs, proc => proc.Id == _cachedProcId)) 142 | return _cachedHWnd; 143 | 144 | foreach (var proc in procs) 145 | { 146 | foreach (ProcessThread thread in proc.Threads) 147 | { 148 | Win32.EnumThreadWindows(thread.Id, (hWnd, lParam) => 149 | { 150 | var sb = new StringBuilder(256); 151 | 152 | // get the class name to check if it's of type Chome_WidgetWin_0 153 | var ret = Win32.GetClassName(hWnd, sb, sb.Capacity); 154 | 155 | if (ret != 0) 156 | { 157 | if (sb.ToString() == "Chrome_WidgetWin_0") 158 | { 159 | 160 | // now check to make sure that it has a title (Spotify has a couple of windows 161 | // that it uses for specific controls, we don't want those 162 | ret = Win32.GetWindowText(hWnd, sb, sb.Capacity); 163 | if (ret != 0) 164 | { 165 | if (!string.IsNullOrWhiteSpace(sb.ToString())) 166 | { 167 | rv = hWnd; 168 | 169 | _cachedProcId = proc.Id; 170 | _cachedHWnd = hWnd; 171 | 172 | // stop the enumeration immediately 173 | return false; 174 | } 175 | } 176 | } 177 | } 178 | 179 | return true; 180 | }, IntPtr.Zero); 181 | 182 | if (rv != IntPtr.Zero) 183 | { 184 | return rv; 185 | } 186 | } 187 | } 188 | 189 | // couldn't find the window 190 | return rv; 191 | } 192 | 193 | public static bool IsRunning() 194 | { 195 | return (GetSpotify() != IntPtr.Zero); 196 | } 197 | 198 | public static Song GetCurrentSong() 199 | { 200 | if (!Spotify.IsRunning()) 201 | return null; 202 | 203 | IntPtr hWnd = GetSpotify(); 204 | int length = Win32.GetWindowTextLength(hWnd); 205 | StringBuilder sb = new StringBuilder(length + 1); 206 | Win32.GetWindowText(hWnd, sb, sb.Capacity); 207 | 208 | string title = sb.ToString(); 209 | 210 | if (!string.IsNullOrWhiteSpace(title) && title != "Spotify") 211 | { 212 | // Unfortunately we don't have a great way to get the title from Spotify 213 | // so we need to do some gymnastics. 214 | // Music played from an artist's page is usually in the format "artist - song" 215 | // while music played from a playlist is often in the format "artist - song - album" 216 | // unfortunately this means that some songs that actually have a " - " in either the artist name 217 | // or in the song name will potentially display incorrectly 218 | var portions = title.Split(new string[] { " - " }, StringSplitOptions.None); 219 | 220 | var song = (portions.Length > 1 ? portions[1] : null); 221 | var artist = portions[0]; 222 | var album = (portions.Length > 2 ? string.Join(" ", portions.Skip(2).ToArray()) : null); // take everything else that's left 223 | 224 | return new Song(artist, song, album); 225 | } 226 | 227 | return null; 228 | } 229 | 230 | private static async Task GetCoverArt(string artist, string track) 231 | { 232 | string imageUrl = null; 233 | 234 | var spotifyWeb = await SpotifyApiClient.GetAsync(); 235 | 236 | var searchRequest = new SearchRequest(SearchRequest.Types.Track, $"{track} artist:{artist}"); 237 | var searchResponse = await spotifyWeb.Search.Item(searchRequest); 238 | 239 | if (searchResponse.Tracks.Items.Count > 0) 240 | { 241 | var images = searchResponse.Tracks.Items[0].Album.Images; 242 | 243 | // iterate through all of the images, finding the smallest ones. This is usually the last 244 | // one, but there is no guarantee in the docs. 245 | var smallestWidth = int.MaxValue; 246 | 247 | foreach (var image in images) 248 | { 249 | if (image.Width < smallestWidth) 250 | { 251 | imageUrl = image.Url; 252 | } 253 | } 254 | } 255 | 256 | return imageUrl; 257 | } 258 | 259 | public static async Task SetCoverArt(Song song) 260 | { 261 | // probably an ad, don't bother looking for an image 262 | if (string.IsNullOrWhiteSpace(song.Track) || string.IsNullOrWhiteSpace(song.Artist)) 263 | return; 264 | 265 | // remove characters known to intefere with searching 266 | var reRemoveChars = new Regex("[/()\"]"); 267 | 268 | var artist = reRemoveChars.Replace(song.Artist, ""); 269 | var track = reRemoveChars.Replace(song.Track, ""); 270 | 271 | var imageUrl = await GetCoverArt(artist, track); 272 | 273 | // sometimes the words in brackets of a song name throw the search off, so if we couldn't find 274 | // any songs, try removing the extra words completely 275 | if (string.IsNullOrWhiteSpace(imageUrl) && (song.Artist.Contains("(") || song.Track.Contains("("))) 276 | { 277 | var reRemoveBrackets = new Regex(@"\(.*\)"); 278 | 279 | artist = reRemoveChars.Replace(reRemoveBrackets.Replace(song.Artist, ""), ""); 280 | track = reRemoveChars.Replace(reRemoveBrackets.Replace(song.Track, ""), ""); 281 | 282 | imageUrl = await GetCoverArt(artist, track); 283 | 284 | } 285 | 286 | if (imageUrl != null) 287 | { 288 | song.CoverArtUrl = imageUrl; 289 | } 290 | 291 | } 292 | 293 | private static bool IsMinimized() 294 | { 295 | if (!Spotify.IsRunning()) 296 | return false; 297 | 298 | var hWnd = Spotify.GetSpotify(); 299 | 300 | // check Spotify's current window state 301 | var placement = new Win32.WINDOWPLACEMENT(); 302 | Win32.GetWindowPlacement(hWnd, ref placement); 303 | 304 | return (placement.showCmd == Win32.Constants.SW_SHOWMINIMIZED); 305 | } 306 | 307 | /* 308 | *TODO: Establish is this is needed. Could be useful to bring Spotify to the front without changing focus? 309 | private static void ShowSpotifyWithNoActivate() 310 | { 311 | var hWnd = Spotify.GetSpotify(); 312 | 313 | // check Spotify's current window state 314 | var placement = new Win32.WINDOWPLACEMENT(); 315 | Win32.GetWindowPlacement(hWnd, ref placement); 316 | 317 | var flags = Win32.SetWindowPosFlags.DoNotActivate | Win32.SetWindowPosFlags.DoNotChangeOwnerZOrder | Win32.SetWindowPosFlags.ShowWindow; 318 | 319 | Win32.SetWindowPos(hWnd, (IntPtr)0, placement.rcNormalPosition.Left, placement.rcNormalPosition.Top, 0, 0, flags); 320 | } 321 | */ 322 | 323 | private static void ShowSpotify() 324 | { 325 | if (Spotify.IsRunning()) 326 | { 327 | var hWnd = Spotify.GetSpotify(); 328 | 329 | // check Spotify's current window state 330 | var placement = new Win32.WINDOWPLACEMENT(); 331 | Win32.GetWindowPlacement(hWnd, ref placement); 332 | 333 | int showCommand = Win32.Constants.SW_SHOW; 334 | 335 | // if Spotify is minimzed we need to send a restore so that the window 336 | // will come back exactly like it was before being minimized (i.e. maximized 337 | // or otherwise) otherwise if we call SW_RESTORE on a currently maximized window 338 | // then instead of staying maximized it will return to normal size. 339 | if (placement.showCmd == Win32.Constants.SW_SHOWMINIMIZED) 340 | { 341 | showCommand = Win32.Constants.SW_RESTORE; 342 | } 343 | 344 | Win32.ShowWindow(hWnd, showCommand); 345 | 346 | Win32.SetForegroundWindow(hWnd); 347 | Win32.SetFocus(hWnd); 348 | } 349 | } 350 | 351 | public static void SendAction(SpotifyAction a) 352 | { 353 | if (!Spotify.IsRunning()) 354 | return; 355 | 356 | // bah. Because control cannot fall through cases we need to special case volume 357 | if (SettingsXml.Current.ChangeSpotifyVolumeOnly) 358 | { 359 | if (a == SpotifyAction.VolumeUp) 360 | { 361 | Telemetry.TrackEvent(TelemetryCategory.Action, Telemetry.TelemetryEvent.Action.VolumeUp); 362 | 363 | VolumeHelper.IncrementVolume("Spotify"); 364 | return; 365 | } 366 | else if (a == SpotifyAction.VolumeDown) 367 | { 368 | Telemetry.TrackEvent(TelemetryCategory.Action, Telemetry.TelemetryEvent.Action.VolumeDown); 369 | 370 | VolumeHelper.DecrementVolume("Spotify"); 371 | return; 372 | } 373 | else if (a == SpotifyAction.Mute) 374 | { 375 | Telemetry.TrackEvent(TelemetryCategory.Action, Telemetry.TelemetryEvent.Action.Mute); 376 | 377 | VolumeHelper.ToggleApplicationMute("Spotify"); 378 | return; 379 | } 380 | } 381 | 382 | switch (a) 383 | { 384 | case SpotifyAction.CopyTrackInfo: 385 | case SpotifyAction.ShowToast: 386 | //Nothing 387 | break; 388 | case SpotifyAction.ShowSpotify: 389 | Telemetry.TrackEvent(TelemetryCategory.Action, Telemetry.TelemetryEvent.Action.ShowSpotify); 390 | 391 | 392 | if (Spotify.IsMinimized()) 393 | { 394 | ShowSpotify(); 395 | } 396 | else 397 | { 398 | Minimize(); 399 | } 400 | 401 | break; 402 | case SpotifyAction.FastForward: 403 | 404 | Telemetry.TrackEvent(TelemetryCategory.Action, Telemetry.TelemetryEvent.Action.FastForward); 405 | 406 | SendComplexKeys("+{Right}"); 407 | break; 408 | 409 | case SpotifyAction.Rewind: 410 | 411 | Telemetry.TrackEvent(TelemetryCategory.Action, Telemetry.TelemetryEvent.Action.Rewind); 412 | 413 | SendComplexKeys("+{Left}"); 414 | break; 415 | 416 | default: 417 | 418 | Telemetry.TrackEvent(TelemetryCategory.Action, Telemetry.TelemetryEvent.Action.Default + a.ToString()); 419 | 420 | Win32.SendMessage(GetSpotify(), Win32.Constants.WM_APPCOMMAND, IntPtr.Zero, new IntPtr((long)a)); 421 | break; 422 | } 423 | } 424 | 425 | /// 426 | /// Some commands require sending keys directly to Spotify (for example, Fast Forward and Rewind which 427 | /// are not handled by Spotify). We can't inject keys directly with WM_KEYDOWN/UP since we need a keyboard 428 | /// hook to actually change the state of various modifier keys (for example, Shift + Right for Fast Forward). 429 | /// 430 | /// AutoHotKey has that hook and can modify the state for us, so let's take advantge of it. 431 | /// 432 | /// 433 | private static void SendComplexKeys(string keys) 434 | { 435 | // Is this nicer? 436 | // _ahk = _ahk ?? new AutoHotkey.Interop.AutoHotkeyEngine(); 437 | 438 | // only initialize AHK when needed as it can be expensive (dll copy etc) if not actually needed 439 | if (_ahk == null) 440 | { 441 | _ahk = new AutoHotkey.Interop.AutoHotkeyEngine(); 442 | } 443 | 444 | _ahk.ExecRaw("SetTitleMatchMode 2"); 445 | 446 | _ahk.ExecRaw("DetectHiddenWindows, On"); 447 | _ahk.ExecRaw("ControlSend, ahk_parent, " + keys + ", ahk_class SpotifyMainWindow"); 448 | 449 | _ahk.ExecRaw("DetectHiddenWindows, Off"); 450 | } 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /Toastify/Settings.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Launch Toastify with Windows 21 | Minimize Spotify on launch 22 | Close Spotify when you close Toastify 23 | When changing volume, change volume only for Spotify 24 | 25 | 26 | 27 | This is the text that will be copied to the clipboard when you hit the "Copy to Clipboard" hotkey. "{0}" will be replaced with the name of the currently playing song. 28 | 29 | 30 | 31 | 32 | 33 | Do not collect anonymous usage data 34 | 35 | Data is never shared, is anonymous, and is used to inform future features 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | Enable Global Hotkeys 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | Disable Toast 88 | 89 | Only Show Toast when Hotkey is pressed 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | The best way to tweak the position of the toast is to hold down Control and then drag the toast around. The raw numbers are still accessible below. Note that the Toast will display at the default position (lower corner above the system time) if you position the toast off-screen. 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | --------------------------------------------------------------------------------