From 02956a4e5a4f90664580c21b958d8f54867e0453 Mon Sep 17 00:00:00 2001 From: Moore Date: Fri, 25 Oct 2024 15:00:08 -0700 Subject: [PATCH] Added Startup options and a bit of refactoring. --- App.config | 15 +++ .../FormHotKeyHelper.Designer.vb | 1 + .../FormHotKeyHelper.resx | 0 .../FormHotKeyHelper.vb | 0 .../FormMain.Designer.vb | 106 +++++++++++------ FormMain.resx => Forms/FormMain.resx | 4 +- FormMain.vb => Forms/FormMain.vb | 61 ++++++++-- HotkeyParser.vb | 43 ------- Modules/NativeMethods.vb | 109 ++++++++++++++++++ Modules/SpotifyHelper.vb | 74 ++++++++++++ My Project/Application.Designer.vb | 6 + My Project/Application.myapp | 1 + My Project/AssemblyInfo.vb | 4 +- My Project/Settings.Designer.vb | 26 ++++- My Project/Settings.settings | 17 ++- NativeMethods.vb | 106 ----------------- SpotifyGlobal.vbproj | 18 +-- SpotifyHelper.vb | 70 ----------- Utils/HotkeyParser.vb | 45 ++++++++ 19 files changed, 425 insertions(+), 281 deletions(-) rename FormHotKeyHelper.Designer.vb => Forms/FormHotKeyHelper.Designer.vb (96%) rename FormHotKeyHelper.resx => Forms/FormHotKeyHelper.resx (100%) rename FormHotKeyHelper.vb => Forms/FormHotKeyHelper.vb (100%) rename FormMain.Designer.vb => Forms/FormMain.Designer.vb (70%) rename FormMain.resx => Forms/FormMain.resx (99%) rename FormMain.vb => Forms/FormMain.vb (74%) delete mode 100644 HotkeyParser.vb create mode 100644 Modules/NativeMethods.vb create mode 100644 Modules/SpotifyHelper.vb delete mode 100644 NativeMethods.vb delete mode 100644 SpotifyHelper.vb create mode 100644 Utils/HotkeyParser.vb diff --git a/App.config b/App.config index 1c75772..2be3eb5 100644 --- a/App.config +++ b/App.config @@ -1,6 +1,21 @@  + + +
+ + + + + + False + + + False + + + \ No newline at end of file diff --git a/FormHotKeyHelper.Designer.vb b/Forms/FormHotKeyHelper.Designer.vb similarity index 96% rename from FormHotKeyHelper.Designer.vb rename to Forms/FormHotKeyHelper.Designer.vb index e6e51d6..2803695 100644 --- a/FormHotKeyHelper.Designer.vb +++ b/Forms/FormHotKeyHelper.Designer.vb @@ -51,6 +51,7 @@ Partial Class FormHotKeyHelper Me.Controls.Add(Me.TextHotKey) Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow Me.Name = "FormHotKeyHelper" + Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent Me.Text = "HotKeyHelper" Me.ResumeLayout(False) Me.PerformLayout() diff --git a/FormHotKeyHelper.resx b/Forms/FormHotKeyHelper.resx similarity index 100% rename from FormHotKeyHelper.resx rename to Forms/FormHotKeyHelper.resx diff --git a/FormHotKeyHelper.vb b/Forms/FormHotKeyHelper.vb similarity index 100% rename from FormHotKeyHelper.vb rename to Forms/FormHotKeyHelper.vb diff --git a/FormMain.Designer.vb b/Forms/FormMain.Designer.vb similarity index 70% rename from FormMain.Designer.vb rename to Forms/FormMain.Designer.vb index f2addc9..5314b51 100644 --- a/FormMain.Designer.vb +++ b/Forms/FormMain.Designer.vb @@ -24,44 +24,48 @@ Partial Class FormMain Private Sub InitializeComponent() Me.components = New System.ComponentModel.Container() Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(FormMain)) - Me.ButtonRegister = New System.Windows.Forms.Button() - Me.LabelStatus = New System.Windows.Forms.Label() Me.ButtonHotKey = New System.Windows.Forms.Button() + Me.LabelStatus = New System.Windows.Forms.Label() + Me.ButtonRegister = New System.Windows.Forms.Button() Me.NotifyIcon1 = New System.Windows.Forms.NotifyIcon(Me.components) Me.ContextMenuStrip1 = New System.Windows.Forms.ContextMenuStrip(Me.components) Me.RestoreToolStripMenuItem = New System.Windows.Forms.ToolStripMenuItem() Me.ExitToolStripMenuItem = New System.Windows.Forms.ToolStripMenuItem() - Me.GitToolStripMenuItem = New System.Windows.Forms.ToolStripMenuItem() Me.ToolStripSeparator1 = New System.Windows.Forms.ToolStripSeparator() + Me.GitToolStripMenuItem = New System.Windows.Forms.ToolStripMenuItem() + Me.CheckAutoReg = New System.Windows.Forms.CheckBox() + Me.GroupBox1 = New System.Windows.Forms.GroupBox() + Me.CheckAutoMin = New System.Windows.Forms.CheckBox() Me.ContextMenuStrip1.SuspendLayout() + Me.GroupBox1.SuspendLayout() Me.SuspendLayout() ' - 'ButtonRegister + 'ButtonHotKey ' - Me.ButtonRegister.Location = New System.Drawing.Point(15, 12) - Me.ButtonRegister.Name = "ButtonRegister" - Me.ButtonRegister.Size = New System.Drawing.Size(219, 31) - Me.ButtonRegister.TabIndex = 0 - Me.ButtonRegister.Text = "Register" - Me.ButtonRegister.UseVisualStyleBackColor = True + Me.ButtonHotKey.Location = New System.Drawing.Point(15, 100) + Me.ButtonHotKey.Name = "ButtonHotKey" + Me.ButtonHotKey.Size = New System.Drawing.Size(219, 31) + Me.ButtonHotKey.TabIndex = 9 + Me.ButtonHotKey.Text = "HotKeyHelper" + Me.ButtonHotKey.UseVisualStyleBackColor = True ' 'LabelStatus ' - Me.LabelStatus.Location = New System.Drawing.Point(12, 83) + Me.LabelStatus.Location = New System.Drawing.Point(12, 134) Me.LabelStatus.Name = "LabelStatus" Me.LabelStatus.Size = New System.Drawing.Size(222, 26) - Me.LabelStatus.TabIndex = 3 + Me.LabelStatus.TabIndex = 8 Me.LabelStatus.Text = "Status: Idle." Me.LabelStatus.TextAlign = System.Drawing.ContentAlignment.MiddleCenter ' - 'ButtonHotKey + 'ButtonRegister ' - Me.ButtonHotKey.Location = New System.Drawing.Point(15, 49) - Me.ButtonHotKey.Name = "ButtonHotKey" - Me.ButtonHotKey.Size = New System.Drawing.Size(219, 31) - Me.ButtonHotKey.TabIndex = 4 - Me.ButtonHotKey.Text = "HotKeyHelper" - Me.ButtonHotKey.UseVisualStyleBackColor = True + Me.ButtonRegister.Location = New System.Drawing.Point(15, 63) + Me.ButtonRegister.Name = "ButtonRegister" + Me.ButtonRegister.Size = New System.Drawing.Size(219, 31) + Me.ButtonRegister.TabIndex = 7 + Me.ButtonRegister.Text = "Register" + Me.ButtonRegister.UseVisualStyleBackColor = True ' 'NotifyIcon1 ' @@ -74,56 +78,92 @@ Partial Class FormMain ' Me.ContextMenuStrip1.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.RestoreToolStripMenuItem, Me.ExitToolStripMenuItem, Me.ToolStripSeparator1, Me.GitToolStripMenuItem}) Me.ContextMenuStrip1.Name = "ContextMenuStrip1" - Me.ContextMenuStrip1.Size = New System.Drawing.Size(157, 88) + Me.ContextMenuStrip1.Size = New System.Drawing.Size(134, 76) ' 'RestoreToolStripMenuItem ' Me.RestoreToolStripMenuItem.Name = "RestoreToolStripMenuItem" - Me.RestoreToolStripMenuItem.Size = New System.Drawing.Size(180, 26) + Me.RestoreToolStripMenuItem.Size = New System.Drawing.Size(133, 22) Me.RestoreToolStripMenuItem.Text = "Restore" ' 'ExitToolStripMenuItem ' Me.ExitToolStripMenuItem.Name = "ExitToolStripMenuItem" - Me.ExitToolStripMenuItem.Size = New System.Drawing.Size(180, 26) + Me.ExitToolStripMenuItem.Size = New System.Drawing.Size(133, 22) Me.ExitToolStripMenuItem.Text = "Exit" ' + 'ToolStripSeparator1 + ' + Me.ToolStripSeparator1.Name = "ToolStripSeparator1" + Me.ToolStripSeparator1.Size = New System.Drawing.Size(130, 6) + ' 'GitToolStripMenuItem ' Me.GitToolStripMenuItem.Name = "GitToolStripMenuItem" - Me.GitToolStripMenuItem.Size = New System.Drawing.Size(180, 26) + Me.GitToolStripMenuItem.Size = New System.Drawing.Size(133, 22) Me.GitToolStripMenuItem.Text = "Homepage" ' - 'ToolStripSeparator1 - ' - Me.ToolStripSeparator1.Name = "ToolStripSeparator1" - Me.ToolStripSeparator1.Size = New System.Drawing.Size(177, 6) + 'CheckAutoReg + ' + Me.CheckAutoReg.AutoSize = True + Me.CheckAutoReg.Location = New System.Drawing.Point(15, 19) + Me.CheckAutoReg.Name = "CheckAutoReg" + Me.CheckAutoReg.Size = New System.Drawing.Size(90, 17) + Me.CheckAutoReg.TabIndex = 5 + Me.CheckAutoReg.Text = "Auto-Register" + Me.CheckAutoReg.UseVisualStyleBackColor = True + ' + 'GroupBox1 + ' + Me.GroupBox1.Controls.Add(Me.CheckAutoMin) + Me.GroupBox1.Controls.Add(Me.CheckAutoReg) + Me.GroupBox1.Location = New System.Drawing.Point(15, 12) + Me.GroupBox1.Name = "GroupBox1" + Me.GroupBox1.Size = New System.Drawing.Size(219, 45) + Me.GroupBox1.TabIndex = 10 + Me.GroupBox1.TabStop = False + Me.GroupBox1.Text = "Statup Options" + ' + 'CheckAutoMin + ' + Me.CheckAutoMin.AutoSize = True + Me.CheckAutoMin.Location = New System.Drawing.Point(122, 19) + Me.CheckAutoMin.Name = "CheckAutoMin" + Me.CheckAutoMin.Size = New System.Drawing.Size(91, 17) + Me.CheckAutoMin.TabIndex = 6 + Me.CheckAutoMin.Text = "Auto-Minimize" + Me.CheckAutoMin.UseVisualStyleBackColor = True ' 'FormMain ' Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font - Me.ClientSize = New System.Drawing.Size(246, 115) + Me.ClientSize = New System.Drawing.Size(246, 159) Me.Controls.Add(Me.ButtonHotKey) Me.Controls.Add(Me.LabelStatus) Me.Controls.Add(Me.ButtonRegister) + Me.Controls.Add(Me.GroupBox1) Me.Icon = CType(resources.GetObject("$this.Icon"), System.Drawing.Icon) - Me.MaximumSize = New System.Drawing.Size(262, 155) - Me.MinimumSize = New System.Drawing.Size(262, 155) Me.Name = "FormMain" + Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen Me.Text = "SpotiKeys" Me.ContextMenuStrip1.ResumeLayout(False) + Me.GroupBox1.ResumeLayout(False) + Me.GroupBox1.PerformLayout() Me.ResumeLayout(False) End Sub - Friend WithEvents ButtonRegister As Button - Friend WithEvents LabelStatus As Label Friend WithEvents ButtonHotKey As Button + Friend WithEvents LabelStatus As Label + Friend WithEvents ButtonRegister As Button Friend WithEvents NotifyIcon1 As NotifyIcon Friend WithEvents ContextMenuStrip1 As ContextMenuStrip Friend WithEvents RestoreToolStripMenuItem As ToolStripMenuItem Friend WithEvents ExitToolStripMenuItem As ToolStripMenuItem - Friend WithEvents GitToolStripMenuItem As ToolStripMenuItem Friend WithEvents ToolStripSeparator1 As ToolStripSeparator + Friend WithEvents GitToolStripMenuItem As ToolStripMenuItem + Friend WithEvents CheckAutoReg As CheckBox + Friend WithEvents GroupBox1 As GroupBox + Friend WithEvents CheckAutoMin As CheckBox End Class diff --git a/FormMain.resx b/Forms/FormMain.resx similarity index 99% rename from FormMain.resx rename to Forms/FormMain.resx index 745596a..c635923 100644 --- a/FormMain.resx +++ b/Forms/FormMain.resx @@ -118,10 +118,10 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 17, 17 + 309, 17 - 152, 17 + 425, 17 diff --git a/FormMain.vb b/Forms/FormMain.vb similarity index 74% rename from FormMain.vb rename to Forms/FormMain.vb index 8df1019..fb2c461 100644 --- a/FormMain.vb +++ b/Forms/FormMain.vb @@ -1,5 +1,7 @@ Imports NHotkey Imports NHotkey.WindowsForms +Imports SpotiKey.Modules +Imports SpotiKey.Utils Public Class FormMain @@ -9,10 +11,8 @@ Public Class FormMain Select Case ButtonRegister.Text Case "Register" CheckSetSpotify() - RegisterHotkeysFromFile($"{Application.StartupPath}\Hotkeys.txt") - HotkeyManager.Current.IsEnabled = True ButtonRegister.Text = "Unregister" - Case "Unregister" + Case "Unregister" - HotkeyManager.Current.IsEnabled = False ButtonRegister.Text = "Register" End Select @@ -40,7 +40,7 @@ Public Class FormMain Dim key = HotkeyParser.ParseKey(keyName) HotkeyManager.Current.AddOrReplace(action, key Or modifiers, AddressOf HotkeyPressed) - Console.WriteLine($"Successfully registered hotkey: {action}") + LabelStatus.Text = "Status: Registered hotkeys!" Catch ex As Exception Console.WriteLine($"Error registering hotkey {action}: {ex.Message}") End Try @@ -69,7 +69,8 @@ Public Class FormMain Private Sub CheckSetSpotify() _spotifyHwnd = SpotifyHelper.GetSpotify() If _spotifyHwnd <> IntPtr.Zero Then - LabelStatus.Text = "Status: Ready!" + RegisterHotkeysFromFile($"{Application.StartupPath}\Hotkeys.txt") + HotkeyManager.Current.IsEnabled = True Else LabelStatus.Text = "Status: Failure!" End If @@ -81,10 +82,7 @@ Public Class FormMain End Sub Private Sub FormMain_Resize(sender As Object, e As EventArgs) Handles Me.Resize - If Me.WindowState = FormWindowState.Minimized Then - Me.Hide() ' Hide the form - NotifyIcon1.Visible = True ' Show the NotifyIcon in the system tray - End If + HideWindow(True) End Sub Private Sub NotifyIcon1_MouseDoubleClick(sender As Object, e As MouseEventArgs) Handles NotifyIcon1.MouseDoubleClick @@ -95,6 +93,21 @@ Public Class FormMain ShowWindow() End Sub + Private Sub HideWindow(resized As Boolean) + + If resized Then + If Me.WindowState = FormWindowState.Minimized Then + Me.Hide() ' Hide the form + NotifyIcon1.Visible = True ' Show the NotifyIcon in the system tray + End If + Else + Me.WindowState = FormWindowState.Minimized + Me.Hide() ' Hide the form + NotifyIcon1.Visible = True ' Show the NotifyIcon in the system tray + End If + + End Sub + Private Sub ShowWindow() Me.Show() ' Show the form Me.WindowState = FormWindowState.Normal ' Restore the window to normal state @@ -113,4 +126,34 @@ Public Class FormMain NotifyIcon1.Visible = False NotifyIcon1.Dispose() End Sub + + Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load + + LoadSettings() + + If My.Settings.AutoRegister Then + ButtonRegister.Enabled = False + CheckSetSpotify() + End If + If My.Settings.AutoMinimize Then + HideWindow(False) + End If + + End Sub + + Private Sub LoadSettings() + CheckAutoMin.Checked = My.Settings.AutoMinimize + CheckAutoReg.Checked = My.Settings.AutoRegister + End Sub + + Private Sub CheckAutoReg_CheckedChanged(sender As Object, e As EventArgs) Handles CheckAutoReg.CheckedChanged + My.Settings.AutoRegister = CheckAutoReg.Checked + My.Settings.Save() + End Sub + + Private Sub CheckAutoMin_CheckedChanged(sender As Object, e As EventArgs) Handles CheckAutoMin.CheckedChanged + My.Settings.AutoMinimize = CheckAutoMin.Checked + My.Settings.Save() + End Sub + End Class \ No newline at end of file diff --git a/HotkeyParser.vb b/HotkeyParser.vb deleted file mode 100644 index 5811046..0000000 --- a/HotkeyParser.vb +++ /dev/null @@ -1,43 +0,0 @@ - -Public Class HotkeyParser - - Private Shared ReadOnly keyMap As Dictionary(Of String, Keys) = InitializeKeyMap() - - Private Shared Function InitializeKeyMap() As Dictionary(Of String, Keys) - Dim map As New Dictionary(Of String, Keys)(StringComparer.OrdinalIgnoreCase) - ' Use reflection to get all values from the Keys enum - Dim values As Array = [Enum].GetValues(GetType(Keys)) - For Each key As Keys In values - Dim keyName As String = [Enum].GetName(GetType(Keys), key) - ' Some Keys enum values map to the same integer value, check before adding - If Not map.ContainsKey(keyName) Then - map.Add(keyName, key) - End If - Next - Return map - End Function - Public Shared Function ParseKey(keyName As String) As Keys - If keyMap.ContainsKey(keyName) Then - Return keyMap(keyName) - Else - Return Keys.None - End If - End Function - - Public Shared Function ParseModifier(modifierString As String) As Keys - Dim modifiers = Keys.None ' Initialize with no modifier. - Dim modifierNames = modifierString.Split("+") - - For Each name In modifierNames - Dim modifier = Keys.None - ' Try to get the modifier key value from the keyMap, fall back to Keys.None if not found. - If keyMap.TryGetValue(name, modifier) Then - ' Combine the modifiers using bitwise OR. - modifiers = modifiers Or modifier - End If - Next - - Return modifiers - End Function - -End Class \ No newline at end of file diff --git a/Modules/NativeMethods.vb b/Modules/NativeMethods.vb new file mode 100644 index 0000000..29e280f --- /dev/null +++ b/Modules/NativeMethods.vb @@ -0,0 +1,109 @@ +Imports System.Runtime.InteropServices +Imports System.Text + +Namespace Modules + + Module NativeMethods + + Public Const SW_HIDE As Integer = 0 + Public Const SW_SHOWNORMAL As Integer = 1 + Public Const SW_NORMAL As Integer = 1 + Public Const SW_SHOWMINIMIZED As Integer = 2 + Public Const SW_SHOWMAXIMIZED As Integer = 3 + Public Const SW_MAXIMIZE As Integer = 3 + Public Const SW_SHOWNOACTIVATE As Integer = 4 + Public Const SW_SHOW As Integer = 5 + Public Const SW_MINIMIZE As Integer = 6 + Public Const SW_SHOWMINNOACTIVE As Integer = 7 + Public Const SW_SHOWNA As Integer = 8 + Public Const SW_RESTORE As Integer = 9 + + Public Const WM_APPCOMMAND As Integer = &H319 + + 'https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-appcommand + Public Enum AppCommand + PlayPause = 14 * 65536 + [Stop] = 13 * 65536 + [Next] = 11 * 65536 + Previous = 12 * 65536 + VolDown = 9 * 65536 + VolUp = 10 * 65536 + Mute = 8 * 65536 + End Enum + + Delegate Function EnumThreadDelegate(hWnd As IntPtr, lParam As IntPtr) As Boolean + + ''' + ''' Sends the specified message to a window or windows. + ''' + ''' A handle to the window whose window procedure will receive the message. + ''' The message to be sent. + ''' Additional message-specific information. + ''' Additional message-specific information. + ''' The result of the message processing; its value depends on the message sent. + + Public Function SendMessage(hWnd As IntPtr, Msg As UInteger, wParam As IntPtr, lParam As IntPtr) As IntPtr + End Function + + ''' + ''' Brings the thread that created the specified window into the foreground and activates the window. + ''' + ''' A handle to the window that should be activated and brought to the foreground. + ''' True if the window was brought to the foreground, False otherwise. + + Public Function SetForegroundWindow(hWnd As IntPtr) As Boolean + End Function + + ''' + ''' Sets the specified window's show state. + ''' + ''' A handle to the window. + ''' Specifies how the window is to be shown. + ''' True if the window was previously visible, False otherwise. + + Public Function ShowWindow(hWnd As IntPtr, nCmdShow As Integer) As Boolean + End Function + + ''' + ''' Sets the show state of a window without waiting for the operation to complete. + ''' + ''' A handle to the window. + ''' Specifies how the window is to be shown. + ''' True if the operation was successful, False otherwise. + + Public Function ShowWindowAsync(hWnd As IntPtr, nCmdShow As Integer) As Boolean + End Function + + ''' + ''' Enumerates all non-child windows associated with a thread by passing the handle to each window, in turn, to an application-defined callback function. + ''' + ''' The thread identifier. + ''' A pointer to an application-defined callback function. + ''' An application-defined value to be passed to the callback function. + ''' True if the function succeeds, False otherwise. + + Public Function EnumThreadWindows(dwThreadId As Integer, lpfn As EnumThreadDelegate, lParam As IntPtr) As Boolean + End Function + + ''' + ''' Retrieves the name of the class to which the specified window belongs. + ''' + ''' A handle to the window and, indirectly, the class to which the window belongs. + ''' The class name string. + ''' The length of the lpClassName buffer. + ''' If the function succeeds, the number of characters copied to the buffer, not including the terminating null character; otherwise 0. + + Public Function GetClassName(hWnd As IntPtr, lpClassName As StringBuilder, nMaxCount As Integer) As Integer + End Function + + ''' + ''' Determines whether the specified window handle identifies an existing window. + ''' + ''' A handle to the window to be tested. + ''' True if the window handle identifies an existing window, False otherwise. + + Public Function IsWindow(hWnd As IntPtr) As Boolean + End Function + + End Module +End Namespace \ No newline at end of file diff --git a/Modules/SpotifyHelper.vb b/Modules/SpotifyHelper.vb new file mode 100644 index 0000000..6fc606a --- /dev/null +++ b/Modules/SpotifyHelper.vb @@ -0,0 +1,74 @@ +Imports System.Text + +Namespace Modules + + Module SpotifyHelper + + Private _cachedSpotifyHandle As IntPtr = IntPtr.Zero + Private _spotifyProcess As Process + + Public Sub SendHotkeyToSpotify(hotkey As String) + + If _spotifyProcess IsNot Nothing AndAlso _spotifyProcess.MainWindowHandle <> IntPtr.Zero Then + + ' Attempt to bring Spotify to the foreground + ShowWindowAsync(_spotifyProcess.MainWindowHandle, SW_RESTORE) + Threading.Thread.Sleep(300) ' Small delay to ensure window has time to respond + + If SetForegroundWindow(_spotifyProcess.MainWindowHandle) Then + SendKeys.SendWait(hotkey) + ' Minimize the Spotify window immediately after sending the hotkey + ShowWindow(_spotifyProcess.MainWindowHandle, SW_MINIMIZE) + Else + MessageBox.Show("Could not set Spotify to the foreground.") + End If + + Else + MessageBox.Show("Spotify process is not running.") + End If + End Sub + + Public Function GetSpotify() As IntPtr + ' Check if the cached handle is still valid + If _cachedSpotifyHandle <> IntPtr.Zero AndAlso IsWindowValid(_cachedSpotifyHandle) Then + Return _cachedSpotifyHandle + End If + + ' If not valid, reset cache and search again + _cachedSpotifyHandle = IntPtr.Zero + + Dim procs = Process.GetProcessesByName("spotify") + + _spotifyProcess = procs.FirstOrDefault(Function(proc) Not String.IsNullOrWhiteSpace(proc.MainWindowTitle)) + + For Each proc In procs + For Each thread In proc.Threads + Dim threadId As Integer = thread.Id + EnumThreadWindows(threadId, Function(hWnd, lParam) + Dim sb As New StringBuilder(256) + GetClassName(hWnd, sb, sb.Capacity) + Dim className = sb.ToString() + If className = "Chrome_WidgetWin_0" Then + _cachedSpotifyHandle = hWnd + Return False ' Stop enumeration + End If + Return True ' Continue enumeration + End Function, IntPtr.Zero) + + If _cachedSpotifyHandle <> IntPtr.Zero Then + Return _cachedSpotifyHandle + End If + Next + Next + + ' Return IntPtr.Zero if Spotify is not found + Return IntPtr.Zero + End Function + + Private Function IsWindowValid(hWnd As IntPtr) As Boolean + Return hWnd <> IntPtr.Zero AndAlso IsWindow(hWnd) + End Function + + End Module + +End Namespace \ No newline at end of file diff --git a/My Project/Application.Designer.vb b/My Project/Application.Designer.vb index 7875c86..9be9f51 100644 --- a/My Project/Application.Designer.vb +++ b/My Project/Application.Designer.vb @@ -34,5 +34,11 @@ Namespace My Protected Overrides Sub OnCreateMainForm() Me.MainForm = Global.SpotiKey.FormMain End Sub + + _ + Protected Overrides Function OnInitialize(ByVal commandLineArgs As System.Collections.ObjectModel.ReadOnlyCollection(Of String)) As Boolean + Me.MinimumSplashScreenDisplayTime = 0 + Return MyBase.OnInitialize(commandLineArgs) + End Function End Class End Namespace diff --git a/My Project/Application.myapp b/My Project/Application.myapp index e6507f6..ec78f88 100644 --- a/My Project/Application.myapp +++ b/My Project/Application.myapp @@ -6,6 +6,7 @@ 0 true 0 + 0 true false \ No newline at end of file diff --git a/My Project/AssemblyInfo.vb b/My Project/AssemblyInfo.vb index e8c5722..3632caa 100644 --- a/My Project/AssemblyInfo.vb +++ b/My Project/AssemblyInfo.vb @@ -30,5 +30,5 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/My Project/Settings.Designer.vb b/My Project/Settings.Designer.vb index 30db703..fc6cbb8 100644 --- a/My Project/Settings.Designer.vb +++ b/My Project/Settings.Designer.vb @@ -15,7 +15,7 @@ Option Explicit On Namespace My _ Partial Friend NotInheritable Class MySettings Inherits Global.System.Configuration.ApplicationSettingsBase @@ -53,6 +53,30 @@ Namespace My Return defaultInstance End Get End Property + + _ + Public Property AutoRegister() As Boolean + Get + Return CType(Me("AutoRegister"),Boolean) + End Get + Set + Me("AutoRegister") = value + End Set + End Property + + _ + Public Property AutoMinimize() As Boolean + Get + Return CType(Me("AutoMinimize"),Boolean) + End Get + Set + Me("AutoMinimize") = value + End Set + End Property End Class End Namespace diff --git a/My Project/Settings.settings b/My Project/Settings.settings index 85b890b..622861d 100644 --- a/My Project/Settings.settings +++ b/My Project/Settings.settings @@ -1,7 +1,12 @@  - - - - - - + + + + + False + + + False + + + \ No newline at end of file diff --git a/NativeMethods.vb b/NativeMethods.vb deleted file mode 100644 index 2da35d4..0000000 --- a/NativeMethods.vb +++ /dev/null @@ -1,106 +0,0 @@ -Imports System.Runtime.InteropServices -Imports System.Text - -Module NativeMethods - - Public Const SW_HIDE As Integer = 0 - Public Const SW_SHOWNORMAL As Integer = 1 - Public Const SW_NORMAL As Integer = 1 - Public Const SW_SHOWMINIMIZED As Integer = 2 - Public Const SW_SHOWMAXIMIZED As Integer = 3 - Public Const SW_MAXIMIZE As Integer = 3 - Public Const SW_SHOWNOACTIVATE As Integer = 4 - Public Const SW_SHOW As Integer = 5 - Public Const SW_MINIMIZE As Integer = 6 - Public Const SW_SHOWMINNOACTIVE As Integer = 7 - Public Const SW_SHOWNA As Integer = 8 - Public Const SW_RESTORE As Integer = 9 - - Public Const WM_APPCOMMAND As Integer = &H319 - - 'https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-appcommand - Public Enum AppCommand - PlayPause = 14 * 65536 - [Stop] = 13 * 65536 - [Next] = 11 * 65536 - Previous = 12 * 65536 - VolDown = 9 * 65536 - VolUp = 10 * 65536 - Mute = 8 * 65536 - End Enum - - Delegate Function EnumThreadDelegate(hWnd As IntPtr, lParam As IntPtr) As Boolean - - ''' - ''' Sends the specified message to a window or windows. - ''' - ''' A handle to the window whose window procedure will receive the message. - ''' The message to be sent. - ''' Additional message-specific information. - ''' Additional message-specific information. - ''' The result of the message processing; its value depends on the message sent. - - Public Function SendMessage(hWnd As IntPtr, Msg As UInteger, wParam As IntPtr, lParam As IntPtr) As IntPtr - End Function - - ''' - ''' Brings the thread that created the specified window into the foreground and activates the window. - ''' - ''' A handle to the window that should be activated and brought to the foreground. - ''' True if the window was brought to the foreground, False otherwise. - - Public Function SetForegroundWindow(hWnd As IntPtr) As Boolean - End Function - - ''' - ''' Sets the specified window's show state. - ''' - ''' A handle to the window. - ''' Specifies how the window is to be shown. - ''' True if the window was previously visible, False otherwise. - - Public Function ShowWindow(hWnd As IntPtr, nCmdShow As Integer) As Boolean - End Function - - ''' - ''' Sets the show state of a window without waiting for the operation to complete. - ''' - ''' A handle to the window. - ''' Specifies how the window is to be shown. - ''' True if the operation was successful, False otherwise. - - Public Function ShowWindowAsync(hWnd As IntPtr, nCmdShow As Integer) As Boolean - End Function - - ''' - ''' Enumerates all non-child windows associated with a thread by passing the handle to each window, in turn, to an application-defined callback function. - ''' - ''' The thread identifier. - ''' A pointer to an application-defined callback function. - ''' An application-defined value to be passed to the callback function. - ''' True if the function succeeds, False otherwise. - - Public Function EnumThreadWindows(dwThreadId As Integer, lpfn As EnumThreadDelegate, lParam As IntPtr) As Boolean - End Function - - ''' - ''' Retrieves the name of the class to which the specified window belongs. - ''' - ''' A handle to the window and, indirectly, the class to which the window belongs. - ''' The class name string. - ''' The length of the lpClassName buffer. - ''' If the function succeeds, the number of characters copied to the buffer, not including the terminating null character; otherwise 0. - - Public Function GetClassName(hWnd As IntPtr, lpClassName As StringBuilder, nMaxCount As Integer) As Integer - End Function - - ''' - ''' Determines whether the specified window handle identifies an existing window. - ''' - ''' A handle to the window to be tested. - ''' True if the window handle identifies an existing window, False otherwise. - - Public Function IsWindow(hWnd As IntPtr) As Boolean - End Function - -End Module diff --git a/SpotifyGlobal.vbproj b/SpotifyGlobal.vbproj index 67dd94d..25ce588 100644 --- a/SpotifyGlobal.vbproj +++ b/SpotifyGlobal.vbproj @@ -85,22 +85,21 @@ - + FormHotKeyHelper.vb - + Form - + Form - + FormMain.vb Form - - - + + True @@ -117,12 +116,13 @@ Settings.settings True + - + FormHotKeyHelper.vb - + FormMain.vb diff --git a/SpotifyHelper.vb b/SpotifyHelper.vb deleted file mode 100644 index bd982d7..0000000 --- a/SpotifyHelper.vb +++ /dev/null @@ -1,70 +0,0 @@ -Imports System.Text - -Module SpotifyHelper - - Private _cachedSpotifyHandle As IntPtr = IntPtr.Zero - Private _spotifyProcess As Process - - Public Sub SendHotkeyToSpotify(hotkey As String) - - If _spotifyProcess IsNot Nothing AndAlso _spotifyProcess.MainWindowHandle <> IntPtr.Zero Then - - ' Attempt to bring Spotify to the foreground - ShowWindowAsync(_spotifyProcess.MainWindowHandle, SW_RESTORE) - Threading.Thread.Sleep(300) ' Small delay to ensure window has time to respond - - If SetForegroundWindow(_spotifyProcess.MainWindowHandle) Then - SendKeys.SendWait(hotkey) - ' Minimize the Spotify window immediately after sending the hotkey - ShowWindow(_spotifyProcess.MainWindowHandle, SW_MINIMIZE) - Else - MessageBox.Show("Could not set Spotify to the foreground.") - End If - - Else - MessageBox.Show("Spotify process is not running.") - End If - End Sub - - Public Function GetSpotify() As IntPtr - ' Check if the cached handle is still valid - If _cachedSpotifyHandle <> IntPtr.Zero AndAlso IsWindowValid(_cachedSpotifyHandle) Then - Return _cachedSpotifyHandle - End If - - ' If not valid, reset cache and search again - _cachedSpotifyHandle = IntPtr.Zero - - Dim procs = Process.GetProcessesByName("spotify") - - _spotifyProcess = procs.FirstOrDefault(Function(proc) Not String.IsNullOrWhiteSpace(proc.MainWindowTitle)) - - For Each proc In procs - For Each thread In proc.Threads - Dim threadId As Integer = thread.Id - EnumThreadWindows(threadId, Function(hWnd, lParam) - Dim sb As New StringBuilder(256) - GetClassName(hWnd, sb, sb.Capacity) - Dim className = sb.ToString() - If className = "Chrome_WidgetWin_0" Then - _cachedSpotifyHandle = hWnd - Return False ' Stop enumeration - End If - Return True ' Continue enumeration - End Function, IntPtr.Zero) - - If _cachedSpotifyHandle <> IntPtr.Zero Then - Return _cachedSpotifyHandle - End If - Next - Next - - ' Return IntPtr.Zero if Spotify is not found - Return IntPtr.Zero - End Function - - Private Function IsWindowValid(hWnd As IntPtr) As Boolean - Return hWnd <> IntPtr.Zero AndAlso IsWindow(hWnd) - End Function - -End Module \ No newline at end of file diff --git a/Utils/HotkeyParser.vb b/Utils/HotkeyParser.vb new file mode 100644 index 0000000..8598e37 --- /dev/null +++ b/Utils/HotkeyParser.vb @@ -0,0 +1,45 @@ +Namespace Utils + Public Class HotkeyParser + + Private Shared ReadOnly keyMap As Dictionary(Of String, Keys) = InitializeKeyMap() + + Private Shared Function InitializeKeyMap() As Dictionary(Of String, Keys) + Dim map As New Dictionary(Of String, Keys)(StringComparer.OrdinalIgnoreCase) + ' Use reflection to get all values from the Keys enum + Dim values As Array = [Enum].GetValues(GetType(Keys)) + For Each key As Keys In values + Dim keyName As String = [Enum].GetName(GetType(Keys), key) + ' Some Keys enum values map to the same integer value, check before adding + If Not map.ContainsKey(keyName) Then + map.Add(keyName, key) + End If + Next + Return map + End Function + Public Shared Function ParseKey(keyName As String) As Keys + If keyMap.ContainsKey(keyName) Then + Return keyMap(keyName) + Else + Return Keys.None + End If + End Function + + Public Shared Function ParseModifier(modifierString As String) As Keys + Dim modifiers = Keys.None ' Initialize with no modifier. + Dim modifierNames = modifierString.Split("+") + + For Each name In modifierNames + Dim modifier = Keys.None + ' Try to get the modifier key value from the keyMap, fall back to Keys.None if not found. + If keyMap.TryGetValue(name, modifier) Then + ' Combine the modifiers using bitwise OR. + modifiers = modifiers Or modifier + End If + Next + + Return modifiers + End Function + + End Class + +End Namespace \ No newline at end of file