Skip to content

Instantly share code, notes, and snippets.

@jcsteh
Last active April 28, 2024 13:12
Show Gist options
  • Star 103 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jcsteh/7ccbc6f7b1b7eb85c1c14ac5e0d65195 to your computer and use it in GitHub Desktop.
Save jcsteh/7ccbc6f7b1b7eb85c1c14ac5e0d65195 to your computer and use it in GitHub Desktop.
AutoHotkey script to control Spotify with global keyboard shortcuts
; SpotifyGlobalKeys.ahk:
; AutoHotkey script to control Spotify with global keyboard shortcuts
; Author: James Teh <jamie@jantrid.net>
; Copyright 2017-2018 James Teh
; License: GNU General Public License version 2.0
DetectHiddenWindows, On
; Get the HWND of the Spotify main window.
getSpotifyHwnd() {
WinGet, spotifyHwnd, ID, ahk_exe spotify.exe
Return spotifyHwnd
}
; Send a key to Spotify.
spotifyKey(key) {
spotifyHwnd := getSpotifyHwnd()
; Chromium ignores keys when it isn't focused.
; Focus the document window without bringing the app to the foreground.
ControlFocus, Chrome_RenderWidgetHostHWND1, ahk_id %spotifyHwnd%
ControlSend, , %key%, ahk_id %spotifyHwnd%
Return
}
; Win+alt+p: Play/Pause
#!p::
{
spotifyKey("{Space}")
Return
}
; Win+alt+down: Next
#!Down::
{
spotifyKey("^{Right}")
Return
}
; Win+alt+up: Previous
#!Up::
{
spotifyKey("^{Left}")
Return
}
; Win+alt+right: Seek forward
#!Right::
{
spotifyKey("+{Right}")
Return
}
; Win+alt+left: Seek backward
#!Left::
{
spotifyKey("+{Left}")
Return
}
; shift+volumeUp: Volume up
+Volume_Up::
{
spotifyKey("^{Up}")
Return
}
; shift+volumeDown: Volume down
+Volume_Down::
{
spotifyKey("^{Down}")
Return
}
; Win+alt+o: Show Spotify
#!o::
{
spotifyHwnd := getSpotifyHwnd()
WinGet, style, Style, ahk_id %spotifyHwnd%
if (style & 0x10000000) { ; WS_VISIBLE
WinHide, ahk_id %spotifyHwnd%
} Else {
WinShow, ahk_id %spotifyHwnd%
WinActivate, ahk_id %spotifyHwnd%
}
Return
}
@richo67
Copy link

richo67 commented Sep 28, 2020

Tt doesn't work in the tray

@JohnsonQu1999 I have moved DetectHiddenWindows, On into the function getSpotifyHwnd() and the it works for me 100%

@Heraes-git
Copy link

Heraes-git commented Dec 14, 2020

Doesn't work for me.
Edit : it turns out that any script won't work on my Windows 10, like this simple one :

Esc::
MsgBox, Escape!!!!
return

I don't understand why.

Edit2 : I fixed the problem, but still no reaction from your script.

@Heraes-git
Copy link

There's no mention of DllCall("GetWindow") in this documentation : https://www.autohotkey.com/docs/commands/DllCall.htm
There's only DllCall("GetWindowRect").

@jcsteh
Copy link
Author

jcsteh commented Dec 14, 2020

Doesn't work for me.

FWIW, I now use something else and haven't used this script in a long time, so it wouldn't surprise me if something broke... although there have been other reports saying that it works.

There's no mention of DllCall("GetWindow") in this documentation : https://www.autohotkey.com/docs/commands/DllCall.htm
There's only DllCall("GetWindowRect").

GetWindowRect in that documentation is just an example. DllCall lets you call any function inside a dll. In this case, I'm calling GetWindow from user32.dll.

@Heraes-git
Copy link

Heraes-git commented Dec 14, 2020

After 2 hours of tests, I found that if an other Chrome Window with Twitch is opened (actually, it's the "Twich desktop app"), the command is send to this window instead of the Spotify one. By closing Twitch, I fixed the problem and now your script is working.

Edit : but it doesn't work when the windows is minimized. Moving DetectHiddenWindows, On inside functions doesn't fix it.

Edit2 : PostMessage, 0x319,, 0xE0000,, ahk_id %softwareHwnd% is the correct method to send the command at line 24 of your script.

I'm actually rewritting if to separate the hotkeys mapping form the logic, and also to make the code more "universal". I renamed the function "sendKeyToSoftware()".

sendKeyToSoftware.ahk :

; Get the HWND of the software's main window.
getSoftwareHwnd(processExe) {
	WinGet, softwareHwnd, ID, ahk_exe %processExe%
	; We need the app's third top level window, so get next twice.
	softwareHwnd := DllCall("GetWindow", "uint", softwareHwnd, "uint", 2)
	softwareHwnd := DllCall("GetWindow", "uint", softwareHwnd, "uint", 2)
	Return softwareHwnd
}

; Send a key to a software.
sendKeyToSoftware(processExe, key, method) {
	softwareHwnd := getsoftwareHwnd(processExe)
	if (method == "PostMessage" ) {
		PostMessage, 0x319,, 0xE0000,, ahk_id %softwareHwnd%
	}
	if (method == "ControlSend" ) {
		; Chromium ignores keys when it isn't focused.
		; Focus the document window without bringing the app to the foreground.
		ControlFocus, Chrome_RenderWidgetHostHWND1, ahk_id %softwareHwnd%
		ControlSend, , %key%, ahk_id %softwareHwnd%
	}
	Return
}

It now accepts several parameters, as this, in a separate file my_hotkeys.ahk :

;------------------------------------
; AUTOHOTKEY'S HOTKEYS :
;------------------------------------
; 	# = Windows Key
;	! = ALT
;------------------------------------

;------------------------------------
; COMMANDS SENT {} :
;------------------------------------
;	^ = CTRL
;------------------------------------

#include sendKeyToSoftware.ahk ; loads the logic.

#!p::			sendKeyToSoftware("spotify.exe", "{Space}", "PostMessage") 		; Spotify: Play/Pause
#!Down::		sendKeyToSoftware("spotify.exe", "^{Right}", "PostMessage") 	; Spotify: Next
#!Up::		sendKeyToSoftware("spotify.exe", "^{Left}", "PostMessage") 		; Spotify: Previous
#!Right::		sendKeyToSoftware("spotify.exe", "+{Right}", "PostMessage") 	; Spotify: Seek forward
#!Left::		sendKeyToSoftware("spotify.exe", "+{Left}", "PostMessage") 		; Spotify: Seek backward
+Volume_Up::	sendKeyToSoftware("spotify.exe", "^{Up}", "PostMessage") 		; Spotify: Volume up
+Volume_Down::	sendKeyToSoftware("spotify.exe", "^{Down}", "PostMessage") 		; Spotify: Volume down

; Win+alt+o: Show Spotify
#!o::
{
	softwareHwnd := getsoftwareHwnd("spotify.exe")
	WinGet, style, Style, ahk_id %softwareHwnd%
	if (style & 0x10000000) { ; WS_VISIBLE
		WinHide, ahk_id %softwareHwnd%
	} Else {
		WinShow, ahk_id %softwareHwnd%
		WinActivate, ahk_id %softwareHwnd%
	}
	Return
}

Feel free to inspire from my modifications, I never used the fork feature of Github, I don't know exactly how to use it.

@jcsteh
Copy link
Author

jcsteh commented Dec 15, 2020 via email

@swstim
Copy link

swstim commented Jan 21, 2021

I modified/simplified this a bit to work for my needs. @Romarain pointed me in the right direction with PostMessage, which is sending a WM_APPCOMMAND (0x319) message. Here's a new function with some possible parameters:

VOLUME_MUTE            := 0x80000
VOLUME_DOWN            := 0x90000
VOLUME_UP              := 0xA0000
MEDIA_NEXTTRACK        := 0xB0000
MEDIA_PREVIOUSTRACK    := 0xC0000
MEDIA_STOP             := 0xD0000
MEDIA_PLAY_PAUSE       := 0xE0000

SendAppCommand(exe, command)
{
    PostMessage, 0x319,, %command%,, ahk_exe %exe% ; 0x319 is WM_APPCOMMAND
}

; Example usage for Win+P as pause/play:
#p:: SendAppCommand("spotify.exe", MEDIA_PLAY_PAUSE)

Hope this helps someone coming across this.

Edit The above does have some odd behavior when there are multiple apps open that respond to media keys. My guess is Spotify is passing the message into the shell as described here.

An alternative, specific to spotify, that works only when not minimized is like above:

SendAppCommandToSpotify(command) ; Only works when not minimized
{
    WinGet, spotifyHwnd, ID, ahk_exe spotify.exe
    spotifyHwnd:= DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
    spotifyHwnd:= DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
    ControlFocus, Chrome_RenderWidgetHostHWND1, ahk_id %spotifyHwnd%
    PostMessage, 0x319,, %command%, Chrome_RenderWidgetHostHWND1, ahk_id %spotifyHwnd%
}

@carstenknoch
Copy link

Excellent — thanks so much. I had spent hours trying to figure out how to do this to no avail.

@OmTatSat
Copy link

OmTatSat commented Aug 13, 2021

In the network did not find any fully operating variant.
For this I share my variant, in some places the code from other authors is used.
Hotkeys work always.
When minimized and closing Spotify window, script hides it in the tray.
Hotkeys experienced on the numpad keyboard.
You can enable switching songs by clicking "NUMPAD /" as internal hotkets Spotify and media Windows keys - on Win10 shows the OSD of the switched song.
Added permanent additional hotkes on the Windows media keys - for those who have no keyboard multimedia.

http://forum.script-coding.com/viewtopic.php?id=16534
`
SetBatchLines, -1
globalmediakeys := 1
firstrun := 1
;WinGetTitle, activewin, A
WinGet, activewin , ID, A
DetectHiddenWindows, On
IfWinNotExist, ahk_exe Spotify.exe
{
RunWait Spotify.exe, % A_AppData "\Spotify"
sleep, 2000
}
else Run Spotify.exe
global wntitle :="ahk_id"getSpotifyHwnd()
;ControlFocus, Chrome_RenderWidgetHostHWND1, %wntitle%
sleep, 1400
send, !{tab}
sleep, 200
;send, !{tab}
WinActivate, "ahk_id"activewin
;ToolTip, %activewin%

Numpad5:: ; Play/Pause
{
if (globalmediakeys = 1)
Send {Media_Play_Pause}
else
spotifyKey("{Space}")
return
}
Numpad8:: ; Next
{
if (globalmediakeys = 1)
{
;ToolTip, global
Send {Media_Next}
return
}
else
spotifyKey("^{Right}")
return
}
Numpad2:: ; Previous
{
if (globalmediakeys = 1)
Send {Media_Prev}
else
spotifyKey("^{Left}")
return
}
Numpad6::spotifyKey("+{Right}") ; Seek forward
Numpad4::spotifyKey("+{Left}") ; Seek backward
NumpadAdd:: ; Volume up
{
if (globalmediakeys = 1)
Send {Volume_Up}
else
spotifyKey("^{Up}")
return
}
NumpadSub:: ; Volume down
{
if (globalmediakeys = 1)
Send {Volume_Down}
else
spotifyKey("^{Down}")
return
}
Numpad0::
{
IfWinActive, ahk_exe Spotify.exe
{
;ToolTip, act
WinHide, %spotifyHwnd%

	AltTabEmu()
	}
else
	{
		;ToolTip, else
	Run, Spotify.exe, % A_AppData "\Spotify"
	WinActivate, ahk_exe Spotify.exe
	}
return

}

; ON/OFF use win mediakeys to see OSD when changed songs
NumpadDiv::
{
;ToolTip, globalmediakeys=%globalmediakeys%
sleep, 1000
if(globalmediakeys = 1)
{
globalmediakeys := 0
ToolTip, GlobalMediaKeys OFF
sleep, 1000
ToolTip
return
}
if(globalmediakeys = 0)
{
globalmediakeys := 1
ToolTip, GlobalMediaKeys ON
sleep, 1000
ToolTip
return
}
}

#IfWinNotExist, ahk_exe Spotify.exe
Numpad5::
{
WinGet, activewin , ID, A
RunWait Spotify.exe, % A_AppData "\Spotify"
sleep, 2000
global wntitle :="ahk_id"getSpotifyHwnd()
send, !{tab}
WinActivate, "ahk_id"activewin
}
Numpad0::
{
ToolTip, notexist
RunWait Spotify.exe, % A_AppData "\Spotify"
sleep, 2000
global wntitle :="ahk_id"getSpotifyHwnd()
send, !{tab}
sleep, 200
WinActivate, ahk_exe Spotify.exe
return
}
#If

#If MouseIsOverButtons(wntitle)
LButton::
CoordMode, Mouse
MouseGetPos, mX, mY, Hwndc, , 2
winGetPos, mmX, mmY,,, ahk_id%Hwndc%
hwndnow := "ahk_id"Hwndc
posX := mX - mmX
if(posX < 57 OR posX > 113 AND hwndnow != wntitle)
{
WinHide, A
AltTabEmu()
return
}
if(posX > 57 OR posX < 113 AND hwndnow != wntitle)
WinGet, MinMax, MinMax, A
if(MinMax = 0)
WinMaximize, A
else
WinRestore, A
return

;Permanent extra win media hotkeys

!>Space::Send {Media_Play_Pause} ; AltR + Space
!>Left::Send {Media_Prev} ; AltR + Left
!>Right::Send {Media_Next} ; AltR + Right
!>Delete::Send {Volume_Mute} ; AltR + Delete
!>Up::Send {Volume_Up} ; AltR + Up
!>Down::Send {Volume_Down} ; AltR + Down

; Get the HWND of the Spotify main window.
getSpotifyHwnd() {
SetFormat, IntegerFast, hex
WinGet, spotifyHwnd, ID, ahk_exe spotify.exe
; We need the app's third top level window, so get next twice.
spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
Return spotifyHwnd
}

; Send a key to Spotify.
spotifyKey(key) {
;spotifyHwnd := getSpotifyHwnd()
; Chromium ignores keys when it isn't focused.
; Focus the document window without bringing the app to the foreground.

IfWinNotActive, ahk_exe Spotify.exe
	{
		ControlFocus, Chrome_RenderWidgetHostHWND1,  %wntitle%
		;ToolTip, contkeyssend
	ControlSend, , %key%, %wntitle%
	;AltTabEmu()
	return
	}
IfWinActive, ahk_exe Spotify.exe
{
	Send,  %key%
	;ToolTip, keyssend
	return
}
Return

}

AltTabEmu() {
hWnd := WinExist("A")
Loop {
hWnd := DllCall("GetWindow", "Uint", hWnd, "Ptr", 2)
WinGetTitle Title, ahk_id %hWnd%
} Until Title
WinActivate ahk_id %hWnd%
}

MouseIsOverButtons(WinTitle) {
MouseGetPos, , , Win
winGetPos, mmX, mmY, w,h, ahk_id%win%
WinGet, procname, ProcessName , ahk_id%win%
;ToolTip, % WinTitle "n" Win "n" mmX "x" mmY "n" w "x" h "n" "wntitle: " wntitle "`n""procname: " procname

return (procname = "Spotify.exe" AND WinTitle != "ahk_id"Win AND w > 100 AND w < 200)
}`

@iCeParadox64
Copy link

This script has worked perfectly fine for me and my friend up until yesterday for some reason. If anyone else has the same problem, thankfully it's an easy fix. In this part of the code, just remove/comment out the two lines that say spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2) - Seems like for whatever reason, we need the first top level window now, rather than the third.

	WinGet, spotifyHwnd, ID, ahk_exe Spotify.exe
	; We need the app's third top level window, so get next twice.
	spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
	spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
	Return spotifyHwnd
}

@k0t0fey
Copy link

k0t0fey commented Sep 19, 2021

This script has worked perfectly fine for me and my friend up until yesterday for some reason. If anyone else has the same problem, thankfully it's an easy fix. In this part of the code, just remove/comment out the two lines that say spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2) - Seems like for whatever reason, we need the first top level window now, rather than the third.

	WinGet, spotifyHwnd, ID, ahk_exe Spotify.exe
	; We need the app's third top level window, so get next twice.
	spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
	spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
	Return spotifyHwnd
}

thx!

@MattGauthier
Copy link

Hey k0t0fey, super thanks for this. I noticed this week my Spotify script stopped working week and you saved me some time here.

@wesleyxcooper
Copy link

This script has worked perfectly fine for me and my friend up until yesterday for some reason. If anyone else has the same problem, thankfully it's an easy fix. In this part of the code, just remove/comment out the two lines that say spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2) - Seems like for whatever reason, we need the first top level window now, rather than the third.

	WinGet, spotifyHwnd, ID, ahk_exe Spotify.exe
	; We need the app's third top level window, so get next twice.
	spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
	spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
	Return spotifyHwnd
}

You're a lifesaver! thanks for this

@jcsteh
Copy link
Author

jcsteh commented Sep 22, 2021

This script has worked perfectly fine for me and my friend up until yesterday for some reason. If anyone else has the same problem, thankfully it's an easy fix. In this part of the code, just remove/comment out the two lines that say spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2) - Seems like for whatever reason, we need the first top level window now, rather than the third.

Thanks. I'm not maintaining this any more, but since this is a really simple fix and several others have verified it works, I've made the change here.

@xKloob
Copy link

xKloob commented Sep 22, 2021

This script has worked perfectly fine for me and my friend up until yesterday for some reason. If anyone else has the same problem, thankfully it's an easy fix. In this part of the code, just remove/comment out the two lines that say spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2) - Seems like for whatever reason, we need the first top level window now, rather than the third.

Thank you so much!!! And thank you to jcsteh for updating it. After it stopped working i googled the name in the copyright section of the script and it brought me here! I'm so happy i left all the copyright info in the script.

@walbjorn
Copy link

walbjorn commented Nov 4, 2021

Thanks, @jcsteh and others, for your work! It works perfectly for me after the recent changes. This is the best solution to global hotkeys for Spotify that I have yet found.
A note for anyone who has trouble using the hotkeys while running fullscreen applications: make sure to run AutoHotkey with administrator priveleges.

@master-sabi
Copy link

Hello,
I am having trouble using the script. I downloaded, edited the hotkey to my desire, saved, ran as administrator and yet zero response. Is there something im doing wrong? am i missing something?

`; SpotifyGlobalKeys.ahk:
; AutoHotkey script to control Spotify with global keyboard shortcuts
; Author: James Teh jamie@jantrid.net
; Copyright 2017-2018 James Teh
; License: GNU General Public License version 2.0

DetectHiddenWindows, On

; Get the HWND of the Spotify main window.
getSpotifyHwnd() {
WinGet, spotifyHwnd, ID, ahk_exe spotify.exe
Return spotifyHwnd
}

; Send a key to Spotify.
spotifyKey(key) {
spotifyHwnd := getSpotifyHwnd()
; Chromium ignores keys when it isn't focused.
; Focus the document window without bringing the app to the foreground.
ControlFocus, Chrome_RenderWidgetHostHWND1, ahk_id %spotifyHwnd%
ControlSend, , %key%, ahk_id %spotifyHwnd%
Return
}

; Win+alt+p: Play/Pause
#!p::
{
spotifyKey("{Space}")
Return
}

; Win+alt+down: Next
#!Down::
{
spotifyKey("^{Right}")
Return
}

; Win+alt+up: Previous
#!Up::
{
spotifyKey("^{Left}")
Return
}

; Win+alt+right: Seek forward
#!Right::
{
spotifyKey("+{Right}")
Return
}

; Win+alt+left: Seek backward
#!Left::
{
spotifyKey("+{Left}")
Return
}

; Numpad9: Volume up
+Volume_Up::
{
spotifyKey("^{Up}")
Return
}

; Numpad7: Volume down
+Volume_Down::
{
spotifyKey("^{Down}")
Return
}

; Win+alt+o: Show Spotify
#!o::
{
spotifyHwnd := getSpotifyHwnd()
WinGet, style, Style, ahk_id %spotifyHwnd%
if (style & 0x10000000) { ; WS_VISIBLE
WinHide, ahk_id %spotifyHwnd%
} Else {
WinShow, ahk_id %spotifyHwnd%
WinActivate, ahk_id %spotifyHwnd%
}
Return
}
`

@OmTatSat
Copy link

OmTatSat commented Feb 5, 2022

master-sabi you can try this, it is working for me

; Get the HWND of the Spotify main window.
getSpotifyHwnd() {
SetFormat, IntegerFast, hex
WinGet, spotifyHwnd, ID, ahk_exe spotify.exe
; We need the app's third top level window, so get next twice.
spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
Return spotifyHwnd
}

@OmTatSat
Copy link

OmTatSat commented Feb 5, 2022

I am using this scrypt, you can coment or delete Numpad6:: ...nowPlaying(). string, because it is need some extra files.
;http://forum.script-coding.com/viewtopic.php?pid=149241#p149241 ;#Include tooltip_extended.ahk ;ColoredTip:= new ToolTip({ text: "Я цветной ToolTip!nЯ исчезну через 7 сек" , TextColor: "Navy" , BackColor: 0xFFA500, FontName: "Comic Sans MS" , FontSize: 10 , TimeOut: 7000 }) ; будет автоматически удалён через 7000 мс
#SingleInstance, force
#Warn All, Off

			;### Tray icon Menu Changes продолжение в конце скрипта###
			menu, tray, add, Reload This Script, ReloadThisScript

#WinActivateForce
;SetBatchLines, -1
globalmediakeys := 0
WinGet, activewin , ID, A
DetectHiddenWindows, On
run:
IfWinNotExist, ahk_exe Spotify.exe
{

RunWait Spotify.exe, % A_AppData "\Spotify"
sleep, 2000

}
else Run Spotify.exe
global wntitle :="ahk_id"getSpotifyHwnd()
ControlFocus, Chrome_RenderWidgetHostHWND1, %wntitle%
SetFormat, IntegerFast, d

;send, {alt}{down}{right}{down 2}{enter}
;WinMenuSelectItem, ahk_exe Spotify.exe, , 0&, 3&
sleep, 1400
send, !{tab}
sleep, 200
;send, !{tab}
WinActivate, "ahk_id"activewin
spotifyKey("{Space}")
spotifyKey("{Space}")
;ToolTip, %activewin%

global nowPlaying := ""
, spotify := SpotifyStatus("options.ini")

;return ; End of auto-execute

Numpad9::
{
nowPlaying()
;ToolTip, track_id: %track_id%
spotify.like(track_id)
;spotify.unlike(track_id)
global nextsong := 1
Sleep, 2500
gui, destroy
Sleep, 100
nowPlaying()
return
}
Numpad9 & 1:: ;Добавить в медиатеку. Like
{
oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.1.4", 0, wntitle) ; 4.1.2.1.1.1.2.1.1.3.1.1.4
; 4.1.2.1.1.1.2.1.1.3.1.1.3
oAcc.accDoDefaultAction(0)
vOutput := oAcc.accname(0)
ColoredTip:= new ToolTip({ text: vOutput , TextColor: "Black" , BackColor: "Green", FontName: "Comic Sans MS" , FontSize: 10 , TimeOut: 7000 }) ; будет автоматически удалён через 7000 мс
;ToolTip, % vOutput
;Sleep, 5000
;ToolTip
return
}

Numpad3:: ;Мне не нравится этот трек. Dislike
{
oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.1.5", 0, wntitle) ;4.1.2.1.1.1.2.1.1.3.1.1.5
;4.1.2.1.1.1.2.1.1.3.1.1.4
vOutput := oAcc.accname(0)
if (vOutput = "Удалить")
oAcc.accDoDefaultAction(0)
else
{
oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.1.4", 0, wntitle) ;4.1.2.1.1.1.2.1.1.3.1.1.5
;4.1.2.1.1.1.2.1.1.3.1.1.4
vOutput := oAcc.accname(0)
if (vOutput = "Удалить")
oAcc.accDoDefaultAction(0)
}

Sleep, 500
oAcc := Acc_Get("Object", "4.1.2.1.1.1.3.1.1.1", 0, wntitle) ;Мне не нравится этот трек 4.1.2.1.1.1.4.1.1.1
;4.1.2.1.1.1.3.1.1.1
vOutput := oAcc.accname(0)
if (vOutput = "Мне не нравится этот трек")
oAcc.accDoDefaultAction(0)
else
{
oAcc := Acc_Get("Object", "4.1.2.1.1.1.4.1.1.1", 0, wntitle) ;Мне не нравится этот трек
if (vOutput = "Мне не нравится этот трек")
oAcc.accDoDefaultAction(0)
}

;oAcc := Acc_Get("Object", "4.1.2.1.1.1.3.1.1.2", 0, wntitle) ;Мне не нравится группа

; Sleep, 200
; vOutput := oAcc.accname(0)
; oAcc.accDoDefaultAction(0)
;ToolTip, oAcc.accname(0)

ColoredTip:= new ToolTip({ text: vOutput  , TextColor: "Black"    , BackColor: "Red", FontName: "Comic Sans MS" , FontSize: 10 , TimeOut: 7000 })  ; будет автоматически удалён через 7000 мс

;ToolTip, % vOutput
;Sleep, 5000
;ToolTip
return
}

Numpad5:: ; Play/Pause
{
if !WinExist("Spotify OSD")
global ppause := 1
global refresh := 1
if (globalmediakeys = 1)
Send {Media_Play_Pause}
else
spotifyKey("{Space}")

return
}
Numpad8:: ; Next
{
if (globalmediakeys = 1)
{
;ToolTip, global
Send {Media_Next}
return
}
else
spotifyKey("^{Right}")
global refresh := 1
return
}
Numpad2:: ; Previous
{
if (globalmediakeys = 1)
Send {Media_Prev}
else
spotifyKey("^{Left}")
global refresh := 1
return
}
Numpad6:: ; Seek forward
{

/*
;SetTimer, WatchCursor, 5000

if(nextsong = "1")
{
MsgBox, 1
ToolTip, 1500
sleep, 1500
nextsong := 0
ToolTip, 00
}
*/
spotifyKey("+{Right}")
;tooltip()
nowPlaying()
;Send {Media_Play_Pause}
;sleep 150

;Send {Media_Play_Pause}
;Sleep, 250
return
}

WatchCursor:
SetTimer, WatchCursor, off
ToolTip
return

Numpad4:: ; Seek backward
{

SetTimer, WatchCursor, 5000
spotifyKey("+{Left}")
global refresh := 1
oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.2.6.1", 0, wntitle) ; прошло
oAcc.accDoDefaultAction(0)
vOutput1 := oAcc.accname(0)
oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.2.8.1", 0, wntitle) ; осталось
;oAcc.accDoDefaultAction(0)
vOutput := oAcc.accname(0)
oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.1.3", 0, wntitle) ; likedislike
;oAcc.accDoDefaultAction(0)
likedislike := oAcc.accname(0)
if (likedislike = "Удалить из медиатеки")
likedislike := "Like"
else
likedislike := ""
ToolTip, % vOutput1 "-" vOutput "`n" likedislike
return
}
NumpadAdd:: ; Volume up
{
if (globalmediakeys = 1)
Send {Volume_Up}
else
spotifyKey("^{Up}")
return
}
NumpadSub:: ; Volume down
{
if (globalmediakeys = 1)
Send {Volume_Down}
else
spotifyKey("^{Down}")
return
}
Numpad0::
{
IfWinActive, ahk_exe Spotify.exe
{
;ToolTip, act
WinHide, %spotifyHwnd%

	AltTabEmu()
	}
else
	{
		;ToolTip, else
	Run, Spotify.exe, % A_AppData "\Spotify"
	WinActivate, ahk_exe Spotify.exe
	}
return

}

; ON/OFF use win mediakeys to see OSD when changed songs
NumpadDiv::
{
;ToolTip, globalmediakeys=%globalmediakeys%
sleep, 1000
if(globalmediakeys = 1)
{
globalmediakeys := 0
ToolTip, GlobalMediaKeys OFF
sleep, 1000
ToolTip
return
}
if(globalmediakeys = 0)
{
globalmediakeys := 1
ToolTip, GlobalMediaKeys ON
sleep, 1000
ToolTip
return
}
}

#IfWinNotExist, ahk_exe Spotify.exe
Numpad5::
{
WinGet, activewin , ID, A
RunWait Spotify.exe, % A_AppData "\Spotify"
sleep, 2000
global wntitle :="ahk_id"getSpotifyHwnd()
spotifyKey("{Space}")
send, !{tab}
WinActivate, "ahk_id"activewin
return
}
Numpad0::
{
;ToolTip, notexist
RunWait Spotify.exe, % A_AppData "\Spotify"
sleep, 2000
global wntitle :="ahk_id"getSpotifyHwnd()
send, !{tab}
sleep, 200
WinActivate, ahk_exe Spotify.exe
return
}
#If

nowPlaying()
{
global
ppause := 0
refresh := 1
;SetTimer, timer, Off
if !WinExist("Spotify OSD") OR (nextsong = "1")
{
nextsong := 0
res := spotify.currentlyPlaying()
;testi := spotify.music_library()
if (res = "")
{
ToolTip, нет связи?
Process, Close , Spotify.exe
Sleep, 500
gosub, run
WinWait, ahk_exe Spotify.exe
Sleep, 3300
spotifyKey("{Space}")
Sleep, 300
ToolTip
res := spotify.currentlyPlaying()
if (res = "")
MsgBox, нет связи2?
}
;MsgBox, % testi
res := spotify._toJSON(res) ; or JSON.Load(res)
info := { ""
. "album" : res.item.album.name
, "artists" : res.item.artists.0.name ;", " res.item.artists.1.name ", " res.item.album.artists.2.name ", " res.item.album.artists.3.name
, "cover" : res.item.album.images.1.url
, "Progress" : FormatSeconds(res.progress_ms/1000) " - " FormatSeconds(res.item.duration_ms/1000)
, "start_time" : FormatSeconds(res.progress_ms/1000)
, "duration" : FormatSeconds(res.item.duration_ms/1000)
, "id" : res.item.id
, "track" : res.item.name }
info_duration := info.duration
artist0 := ""
try artist0 := res.item.artists.0.name
artist1 := ""
try artist1 := ", "res.item.artists.1.name
artist2 := ""
try artist2 := ", "res.item.artists.2.name
artist3 := ""
try artist3 := ", "res.item.artists.3.name
artist4 := ""
try artist4 := ", "res.item.artists.4.name
artist5 := ""
try artist5 := ", "res.item.artists.5.name
artist6 := ""
try artist6 := ", "res.item.artists.6.name
artists := artist0 artist1 artist2 artist3 artist4 artist5 artist6

;MsgBox % "Artist 0: " artist0
; . "n Artist 1: " artist1 ; . "n Artist 2: " artist2
; . "`n Artist 3: " artist3
UrlDownloadToFile % info.cover, % A_Temp "\img"
; Gui 1:New, +AlwaysOnTop -Caption

   ; Gui Add, Text,  w300 x5, % nowPlaying := "info.artist: " artists " `n " "info.album: "info.album " `n " "info.track: " info.track "`n" "Progress: " info.Progress "`n id:" info.id "`n check_is_liked(id):" is_liked := spotify.check_is_liked(info.id)
   track_id := info.id
   is_liked := spotify.check_is_liked(track_id)
   start_time := info.start_time
  ; ToolTip, is_liked:%is_liked%
if (is_liked = "1")

{
Color := "green"
}
else
{
Color := "black"
}
Gui, Color, %color%

;Gui Show, w300 h525

CoordMode, Mouse, Screen
;DllCall("DestroyWindow", "Ptr", hStatic)
;ifwinnotactive, ahk_exe PotPlayerMini64.exe
Gui, +AlwaysOnTop
Gui, +ToolWindow -0x400000 -DPIScale
Gui, Color, %color%
Gui Add, Picture, w200 h-1 x0 y0, % A_Temp "\img"

Gui, Font, Italic s13 cWhite bold, Times New Roman
Gui, Add, Text, hwndwStatic x210 y10 Center, % info.track ;"n" artists "n" info.Progress ; %vOutput1% "-" %vOutput%
;GuiControlGet, Pos, Pos, %wStatic%
;w1 := PosX2+PosW
Gui, Font, cGray, ;Times New Roman
Gui, Add, Text, hwndw2Static xp y+10 , %artists%
;GuiControlGet, Pos, Pos, %w2Static%
;w2 := PosX
2+PosW
Gui, Font, normal cSilver,
info_Progress := info.Progress
Gui, Add, Text, vvar hwndhStatic xp y+10 , %info_Progress%
SetTimer, progress1, 270
;GuiControlGet, Pos, Pos, %hStatic%
;h := PosY+PosH ;PosY*2+PosH
Gui, Show, Hide
Gui, +LastFound
WinGetPos, x, y, w, h

MouseGetPos, mX, mY
x := mX+15
y := mY+15
SysGet, Mon, Monitor
x := MonRight < x+w ? MonRight-w : x
y := MonBottom < y+h ? MonBottom-h : y
h := h-10
Gui, Show, NA x%x% y%y% w%w% h%h%, Spotify OSD
winset, Transcolor, FF2FFF 255, MouseTextID
;Sleep, 5000
SetTimer, timer1, 5000
;gui, destroy
return
}
else
SetTimer, timer1, 5000
return

progress1:
;oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.2.6.1", 0, wntitle) ; прошло
;oAcc.accDoDefaultAction(0)
;vOutput1 := oAcc.accname(0)
;if (refresh = "1")
;{
;SetTimer, timer1, 5000
refresh := 0
res := spotify.currentlyPlaying()
res := spotify._toJSON(res) ; or JSON.Load(res)

info := { ""
    . "album"       : res.item.album.name
    , "artists"      : res.item.artists.0.name ;", " res.item.artists.1.name ", " res.item.album.artists.2.name ", " res.item.album.artists.3.name
    , "cover"       : res.item.album.images.1.url
    , "Progress" : FormatSeconds(res.progress_ms/1000)
	, "Progress_" : (res.progress_ms/1000)
	, "duration" : FormatSeconds(res.item.duration_ms/1000)
    , "id1" : res.item.id
    , "track"       : res.item.name }

info_Progress := info.Progress
info_Progress_ := info.Progress_
;ToolTip, %info_Progress_%, , , 1
info_duration := info.duration
;start_time := info_Progress
;}
info_duration := info.duration
if (ppause = "0")
{
;info_Progress_:=info_Progress_+0.25
;ToolTip, %info_Progress_% "n" %ppause% , , , 2 ;ToolTip, %info_Progress_% "n - " %info_duration%
;info_Progress := FormatSeconds(info_Progress_)
;info_Progress_ := info.Progress_
track_id1 := info.id1
;ToolTip, % info_Progress_ start_time info_Progress
if(start_time > info_Progress)
refresh := 1
;text := name "n" artist "n" vOutput1 "-" vOutput
if(track_id != track_id1)
{
;ToolTip, starttime > vOutput1
gui, destroy
SetTimer, timer1, Off
SetTimer, progress1, off
;Sleep, 700
nextsong := 1
refresh := 1
return
}
}
text := info_Progress " - " info_duration
GuiControl, Text, var , %text%
; Gui, Show, w300 h300, Progress
;next_track
;ToolTip, progres
return

timer1:
{
SetTimer, timer1, Off
SetTimer, progress1, off
refresh := 1
gui, destroy
return
}

}
return

tooltip()
{
global
;SetTimer, timer, Off
IfWinNotExist, Spotify OSD
{
;oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.1.2.1.1", 0, wntitle) ; название
oAcc := Acc_Get("Object", "4.1.2.1.1", 0, wntitle) ; название + артисты
;oAcc.accDoDefaultAction(0)
name_artists := oAcc.accname(0)
;name := StrReplace(name, "· ", "n" ) RegExMatch(name_artists, "(.*)\·\s+(.*)", match) name := match1 artists := match2 ;ToolTip, artists %artists% ;oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.1.2.2.1", 0, wntitle) ; артист только первый ;oAcc.accDoDefaultAction(0) ;artist := oAcc.accname(0) ;oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.2.6.1", 0, wntitle) ; прошло ;oAcc.accDoDefaultAction(0) ;vOutput1 := oAcc.accname(0) oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.2.8.1", 0, wntitle) ; осталось ;oAcc.accDoDefaultAction(0) vOutput := oAcc.accname(0) oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.1.3", 0, wntitle) ; likedislike ;oAcc.accDoDefaultAction(0) likedislike := oAcc.accname(0) sleep , 100 oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.2.6.1", 0, wntitle) ; прошло ;oAcc.accDoDefaultAction(0) vOutput1 := oAcc.accname(0) starttime := vOutput1 if (likedislike = "Удалить из медиатеки") ;likedislike := "Like" { ;text := name "n" artist "n" vOutput1 "-" vOutput ;"n" likedislike
Color := "green"
;tooltip(color)
}
else
{
;likedislike := ""
; text := name "n" artist "n" vOutput1 "-" vOutput ;"n" likedislike Color := "black" ;tooltip(color) ;ToolTip, % name "n" artist "n" vOutput1 "-" vOutput "n" likedislike
}
SetTimer, progress, 250
CoordMode, Mouse, Screen
;DllCall("DestroyWindow", "Ptr", hStatic)
;ifwinnotactive, ahk_exe PotPlayerMini64.exe
Gui, +AlwaysOnTop
Gui, +ToolWindow -0x400000 -DPIScale
Gui, Color, %color%
Gui, Font, Italic s13 cWhite bold, Times New Roman
Gui, Add, Text, hwndwStatic x10 y10 Center, %name% ;"n" %artist% "n" %vOutput1% "-" %vOutput%
;GuiControlGet, Pos, Pos, %wStatic%
;w1 := PosX2+PosW
Gui, Font, cGray, ;Times New Roman
Gui, Add, Text, hwndw2Static xp y+10 , %artists%
;GuiControlGet, Pos, Pos, %w2Static%
;w2 := PosX
2+PosW
Gui, Font, normal cSilver,
Gui, Add, Text, vvar hwndhStatic xp y+10 , %vOutput1% - %vOutput%

;GuiControlGet, Pos, Pos, %hStatic%
;h := PosY+PosH ;PosY*2+PosH
Gui, Show, Hide
Gui, +LastFound
WinGetPos, x, y, w, h

MouseGetPos, mX, mY
x := mX+15
y := mY+15
SysGet, Mon, Monitor
x := MonRight < x+w ? MonRight-w : x
y := MonBottom < y+h ? MonBottom-h : y
Gui, Show, NA x%x% y%y% w%w% h%h%, Spotify OSD
winset, Transcolor, FF2FFF 255, MouseTextID
;Sleep, 5000
SetTimer, timer, 5000
;gui, destroy
return
}
else
SetTimer, timer, 5000
return

progress:
oAcc := Acc_Get("Object", "4.1.2.1.1.1.2.1.1.3.1.2.6.1", 0, wntitle) ; прошло
oAcc.accDoDefaultAction(0)
vOutput1 := oAcc.accname(0)
;text := name "n" artist "n" vOutput1 "-" vOutput
if(starttime > vOutput1)
{
;ToolTip, starttime > vOutput1
gui, destroy
SetTimer, timer, Off
SetTimer, progress, off
Sleep, 700
nextsong := 1
return
}

GuiControl, Text, var , %vOutput1% - %vOutput%
; Gui, Show, w300 h300, Progress
;next_track
;ToolTip, progres
return

timer:
{
SetTimer, timer, Off
SetTimer, progress, off
gui, destroy
return
}

}

#If MouseIsOverButtons(wntitle)
LButton::
CoordMode, Mouse
MouseGetPos, mX, mY, Hwndc, , 2
winGetPos, mmX, mmY,,, ahk_id%Hwndc%
hwndnow := "ahk_id"Hwndc
posX := mX - mmX
if(posX < 57 OR posX > 113 AND hwndnow != wntitle)
{
WinHide, A
AltTabEmu()
return
}
if(posX > 57 OR posX < 113 AND hwndnow != wntitle)
WinGet, MinMax, MinMax, A
if(MinMax = 0)
WinMaximize, A
else
WinRestore, A
return

;Permanent extra win media hotkeys

!>Space::Send {Media_Play_Pause} ; AltR + Space
!>Left::Send {Media_Prev} ; AltR + Left
!>Right::Send {Media_Next} ; AltR + Right
!>Delete::Send {Volume_Mute} ; AltR + Delete
!>Up::Send {Volume_Up} ; AltR + Up
!>Down::Send {Volume_Down} ; AltR + Down

; Get the HWND of the Spotify main window.
getSpotifyHwnd() {
SetFormat, IntegerFast, hex
WinGet, spotifyHwnd, ID, ahk_exe spotify.exe
; We need the app's third top level window, so get next twice.
spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
spotifyHwnd := DllCall("GetWindow", "uint", spotifyHwnd, "uint", 2)
Return spotifyHwnd
}

; Send a key to Spotify.
spotifyKey(key) {
;spotifyHwnd := getSpotifyHwnd()
; Chromium ignores keys when it isn't focused.
; Focus the document window without bringing the app to the foreground.

IfWinNotActive, ahk_exe Spotify.exe
	{
		ControlFocus, Chrome_RenderWidgetHostHWND1,  %wntitle%
		;ToolTip, contkeyssend
	ControlSend, , %key%, %wntitle%
	;AltTabEmu()
	return
	}
IfWinActive, ahk_exe Spotify.exe
{
	Send,  %key%
	;ToolTip, keyssend
	return
}
Return

}

AltTabEmu() {
hWnd := WinExist("A")
Loop {
hWnd := DllCall("GetWindow", "Uint", hWnd, "Ptr", 2)
WinGetTitle Title, ahk_id %hWnd%
} Until Title
WinActivate ahk_id %hWnd%
}

MouseIsOverButtons(WinTitle) {
MouseGetPos, , , Win
winGetPos, mmX, mmY, w,h, ahk_id%win%
WinGet, procname, ProcessName , ahk_id%win%
;ToolTip, % WinTitle "n" Win "n" mmX "x" mmY "n" w "x" h "n" "wntitle: " wntitle "`n""procname: " procname
return (procname = "Spotify.exe" AND WinTitle != "ahk_id"Win AND w > 100 AND w < 200)
}

FormatSeconds(NumberOfSeconds) ; Convert the specified number of seconds to hh:mm:ss format.
{
time := 19990101 ; Midnight of an arbitrary date.
time += NumberOfSeconds, seconds
FormatTime, mmss, %time%, mm:ss
if (NumberOfSeconds > 3600)
return NumberOfSeconds//3600 ":" mmss
else
return mmss
/*
; Unlike the method used above, this would not support more than 24 hours worth of seconds:
FormatTime, hmmss, %time%, h:mm:ss
return hmmss
*/
}

; http://www.autohotkey.com/board/topic/77303-acc-library-ahk-l-updated-09272012/
; https://dl.dropbox.com/u/47573473/Web%20Server/AHK_L/Acc.ahk
;------------------------------------------------------------------------------
; Acc.ahk Standard Library
; by Sean
; Updated by jethrow:
; Modified ComObjEnwrap params from (9,pacc) --> (9,pacc,1)
; Changed ComObjUnwrap to ComObjValue in order to avoid AddRef (thanks fincs)
; Added Acc_GetRoleText & Acc_GetStateText
; Added additional functions - commented below
; Removed original Acc_Children function
; last updated 2/25/2010
;------------------------------------------------------------------------------

Acc_Init()
{
Static h
If Not h
h:=DllCall("LoadLibrary","Str","oleacc","Ptr")
}
Acc_ObjectFromEvent(ByRef idChild, hWnd, idObject, idChild)
{
Acc_Init()
If DllCall("oleacc\AccessibleObjectFromEvent", "Ptr", hWnd, "UInt", idObject, "UInt", idChild, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0
Return ComObjEnwrap(9,pacc,1), idChild:=NumGet(varChild,8,"UInt")
}

Acc_ObjectFromPoint(ByRef idChild = "", x = "", y = "")
{
Acc_Init()
If DllCall("oleacc\AccessibleObjectFromPoint", "Int64", x==""||y==""?0DllCall("GetCursorPos","Int64",pt)+pt:x&0xFFFFFFFF|y<<32, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0
Return ComObjEnwrap(9,pacc,1), idChild:=NumGet(varChild,8,"UInt")
}

Acc_ObjectFromWindow(hWnd, idObject = -4)
{
Acc_Init()
If DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject&=0xFFFFFFFF, "Ptr", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,"Int64"),"Int64"), "Ptr*", pacc)=0
Return ComObjEnwrap(9,pacc,1)
}

Acc_WindowFromObject(pacc)
{
If DllCall("oleacc\WindowFromAccessibleObject", "Ptr", IsObject(pacc)?ComObjValue(pacc):pacc, "Ptr*", hWnd)=0
Return hWnd
}

Acc_GetRoleText(nRole)
{
nSize := DllCall("oleacc\GetRoleText", "Uint", nRole, "Ptr", 0, "Uint", 0)
VarSetCapacity(sRole, (A_IsUnicode?2:1)*nSize)
DllCall("oleacc\GetRoleText", "Uint", nRole, "str", sRole, "Uint", nSize+1)
Return sRole
}

Acc_GetStateText(nState)
{
nSize := DllCall("oleacc\GetStateText", "Uint", nState, "Ptr", 0, "Uint", 0)
VarSetCapacity(sState, (A_IsUnicode?2:1)*nSize)
DllCall("oleacc\GetStateText", "Uint", nState, "str", sState, "Uint", nSize+1)
Return sState
}

Acc_SetWinEventHook(eventMin, eventMax, pCallback)
{
Return DllCall("SetWinEventHook", "Uint", eventMin, "Uint", eventMax, "Uint", 0, "Ptr", pCallback, "Uint", 0, "Uint", 0, "Uint", 0)
}

Acc_UnhookWinEvent(hHook)
{
Return DllCall("UnhookWinEvent", "Ptr", hHook)
}
/* Win Events:
pCallback := RegisterCallback("WinEventProc")
WinEventProc(hHook, event, hWnd, idObject, idChild, eventThread, eventTime)
{
Critical
Acc := Acc_ObjectFromEvent(idChild, hWnd, idObject, idChild)
; Code Here:
}
*/

; Written by jethrow
Acc_Role(Acc, ChildId=0) {
try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetRoleText(Acc.accRole(ChildId)):"invalid object"
}
Acc_State(Acc, ChildId=0) {
try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetStateText(Acc.accState(ChildId)):"invalid object"
}
Acc_Location(Acc, ChildId=0, byref Position="") { ; adapted from Sean's code
try Acc.accLocation(ComObj(0x4003,&x:=0), ComObj(0x4003,&y:=0), ComObj(0x4003,&w:=0), ComObj(0x4003,&h:=0), ChildId)
catch
return
Position := "x" NumGet(x,0,"int") " y" NumGet(y,0,"int") " w" NumGet(w,0,"int") " h" NumGet(h,0,"int")
return {x:NumGet(x,0,"int"), y:NumGet(y,0,"int"), w:NumGet(w,0,"int"), h:NumGet(h,0,"int")}
}
Acc_Parent(Acc) {
try parent:=Acc.accParent
return parent?Acc_Query(parent):
}
Acc_Child(Acc, ChildId=0) {
try child:=Acc.accChild(ChildId)
return child?Acc_Query(child):
}
Acc_Query(Acc) { ; thanks Lexikos - www.autohotkey.com/forum/viewtopic.php?t=81731&p=509530#509530
try return ComObj(9, ComObjQuery(Acc,"{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1)
}
Acc_Error(p="") {
static setting:=0
return p=""?setting:setting:=p
}
Acc_Children(Acc) {
if ComObjType(Acc,"Name") != "IAccessible"
ErrorLevel := "Invalid IAccessible Object"
else {
Acc_Init(), cChildren:=Acc.accChildCount, Children:=[]
if DllCall("oleacc\AccessibleChildren", "Ptr",ComObjValue(Acc), "Int",0, "Int",cChildren, "Ptr",VarSetCapacity(varChildren,cChildren*(8+2A_PtrSize),0)0+&varChildren, "Int",cChildren)=0 {
Loop %cChildren%
i:=(A_Index-1)
(A_PtrSize2+8)+8, child:=NumGet(varChildren,i), Children.Insert(NumGet(varChildren,i-8)=9?Acc_Query(child):child), NumGet(varChildren,i-8)=9?ObjRelease(child):
return Children.MaxIndex()?Children:
} else
ErrorLevel := "AccessibleChildren DllCall Failed"
}
if Acc_Error()
throw Exception(ErrorLevel,-1)
}
Acc_ChildrenByRole(Acc, Role) {
if ComObjType(Acc,"Name")!="IAccessible"
ErrorLevel := "Invalid IAccessible Object"
else {
Acc_Init(), cChildren:=Acc.accChildCount, Children:=[]
if DllCall("oleacc\AccessibleChildren", "Ptr",ComObjValue(Acc), "Int",0, "Int",cChildren, "Ptr",VarSetCapacity(varChildren,cChildren
(8+2A_PtrSize),0)0+&varChildren, "Int",cChildren)=0 {
Loop %cChildren% {
i:=(A_Index-1)
(A_PtrSize2+8)+8, child:=NumGet(varChildren,i)
if NumGet(varChildren,i-8)=9
AccChild:=Acc_Query(child), ObjRelease(child), Acc_Role(AccChild)=Role?Children.Insert(AccChild):
else
Acc_Role(Acc, child)=Role?Children.Insert(child):
}
return Children.MaxIndex()?Children:, ErrorLevel:=0
} else
ErrorLevel := "AccessibleChildren DllCall Failed"
}
if Acc_Error()
throw Exception(ErrorLevel,-1)
}
Acc_Get(Cmd, ChildPath="", ChildID=0, WinTitle="", WinText="", ExcludeTitle="", ExcludeText="") {
static properties := {Action:"DefaultAction", DoAction:"DoDefaultAction", Keyboard:"KeyboardShortcut"}
AccObj := IsObject(WinTitle)? WinTitle
: Acc_ObjectFromWindow( WinExist(WinTitle, WinText, ExcludeTitle, ExcludeText), 0 )
if ComObjType(AccObj, "Name") != "IAccessible"
ErrorLevel := "Could not access an IAccessible Object"
else {
StringReplace, ChildPath, ChildPath, _, %A_Space%, All
AccError:=Acc_Error(), Acc_Error(true)
Loop Parse, ChildPath, ., %A_Space%
try {
if A_LoopField is digit
Children:=Acc_Children(AccObj), m2:=A_LoopField ; mimic "m2" output in else-statement
else
RegExMatch(A_LoopField, "(\D
)(\d*)", m), Children:=Acc_ChildrenByRole(AccObj, m1), m2:=(m2?m2:1)
if Not Children.HasKey(m2)
throw
AccObj := Children[m2]
} catch {
ErrorLevel:="Cannot access ChildPath Item #" A_Index " -> " A_LoopField, Acc_Error(AccError)
if Acc_Error()
throw Exception("Cannot access ChildPath Item", -1, "Item #" A_Index " -> " A_LoopField)
return
}
Acc_Error(AccError)
StringReplace, Cmd, Cmd, %A_Space%, , All
properties.HasKey(Cmd)? Cmd:=properties[Cmd]:
try {
if (Cmd = "Location")
AccObj.accLocation(ComObj(0x4003,&x:=0), ComObj(0x4003,&y:=0), ComObj(0x4003,&w:=0), ComObj(0x4003,&h:=0), ChildId)
, ret_val := "x" NumGet(x,0,"int") " y" NumGet(y,0,"int") " w" NumGet(w,0,"int") " h" NumGet(h,0,"int")
else if (Cmd = "Object")
ret_val := AccObj
else if Cmd in Role,State
ret_val := Acc_%Cmd%(AccObj, ChildID+0)
else if Cmd in ChildCount,Selection,Focus
ret_val := AccObj["acc" Cmd]
else
ret_val := AccObj"acc" Cmd
} catch {
ErrorLevel := """" Cmd """ Cmd Not Implemented"
if Acc_Error()
throw Exception("Cmd Not Implemented", -1, Cmd)
return
}
return ret_val, ErrorLevel:=0
}
if Acc_Error()
throw Exception(ErrorLevel,-1)
}

oTimer := Func("Timer").Bind(ColoredTip)
SetTimer, % oTimer, 1000 ; этот таймер только для изменения текста
Sleep, 2500
myToolTip.Hide() ; скрываем
myToolTip.SetText("Можно менять`nтекст и расположение") ; меняем текст
Sleep, 500

myToolTip.Show(50, 50) ; показываем в координатах x = 50, y = 50
Sleep, 2000

myToolTip.Hide()
myToolTip.SetText("I'm near the mouse cursor!")
Sleep, 500

myToolTip.Show() ; без координат — в районе курсора
Sleep, 2500

myToolTip := "" ; уничтожаем ToolTip, это можно было сделать автоматически, указав TimeOut

myTrayTip.SetTItle(2, "Изменённый заголовок") ; меняем иконку и заголовок
myTrayTip.SetText("Goodbye!")
Sleep, 1000
myTrayTip.Destroy()
Sleep, 1000
ExitApp

class ToolTip {
/*
Версия: 1.03 — добавлена возможность смены заголовка

При создании экземпляра объекта в конструктор передаётся ассоциативный массив с опциями.
Возможные ключи:

title
text
icon (1: Info, 2: Warning; 3: Error; n > 3: предполагается hIcon)
CloseButton (true или false)
transparent (true или false)
x, y — координаты, если не указаны, ToolTip появится вблизи курсора
BalloonTip (BalloonTip — это ToolTip с хвостиком, true или false)
TrayTip — будет показан BalloonTip у иконки скрипта в трее, параметры x, y, и BalloonTip игнорируются
FontName
FontSize
FontStyle (bold, italic, underline, strikeout в любом сочетании через пробел)
TimeOut — время, через которое ToolTip будет уничтожен
BackColor — цвет фона
TextColor — цвет текста

Для указания цвета можно использовать литеральные названия, перечисленные здесь:
https://autohotkey.com/docs/commands/Progress.htm#colors
В этом случае название должно быть в кавычках.

Ключи можно задавать в любом порядке и в любой комбинации, как показано в примерах использования.
Если указан ключ TrayTip, удалить окно можно только методом Destroy(),
если нет, тогда можно просто прировнять ссылку на объект пустому значению.

После создания экземпляра объекта можно менять:
расположение — метод Show(x, y), x и y — координаты, если пустые значения — ToolTip будет показан возле курсора
видимость — методы Show() и Hide()
текст — метод SetText(text)
иконку и заголовок — метод SetTitle(icon, title)
*/
__New( options ) {
for k, v in options
this[k] := v
this._CreateToolTip()
this.Show()
}

_CreateToolTip() {
static WS_POPUP := 0x80000000, WS_EX_TOPMOST := 8, WS_EX_TRANSPARENT := 0x20
, TTS_NOPREFIX := 2, TTS_ALWAYSTIP := 1, TTS_BALLOON := 0x40, TTS_CLOSE := 0x80
, TTF_TRACK := 0x20, TTF_ABSOLUTE := 0x80, szTI := A_PtrSize = 4 ? 48 : 72

  VarSetCapacity(TOOLINFO, szTI, 0)
  this.pTI := &TOOLINFO
  NumPut(szTI, TOOLINFO)
  (this.TrayTip && this.BalloonTip := true)
  NumPut(TTF_TRACK|(this.BalloonTip ? 0 : TTF_ABSOLUTE), &TOOLINFO + 4)
  
  this.hwnd := DllCall("CreateWindowEx", UInt, WS_EX_TOPMOST|(this.transparent ? WS_EX_TRANSPARENT : 0)
    , Str, "tooltips_class32", Str, ""
    , UInt, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | (this.CloseButton ? TTS_CLOSE : 0) | (this.BalloonTip ? TTS_BALLOON : 0)
    , Int, 0, Int, 0, Int, 0, Int, 0, Ptr, 0, Ptr, 0, Ptr, 0, Ptr, 0)
    
  if (this.FontName || this.FontSize || this.FontStyle)
     this._SetFont()
  
  if (this.TextColor != "" || this.BackColor != "")
     this._SetColor()
  
  this._SetInfo()

  if this.TimeOut  {
     timer := ObjBindMethod(this, "Destroy")
     SetTimer % timer, % "-" . this.TimeOut
  }

}

_SetFont() {
static WM_GETFONT := 0x31, mult := A_IsUnicode ? 2 : 1, szLF := 28 + 32 * mult
, LOGPIXELSY := 90, ANTIALIASED_QUALITY := 4
, styles := { bold: {value: 700, offset: 16, size: "Int"}
, italic: {value: 1, offset: 20, size: "Char"}
, underline: {value: 1, offset: 21, size: "Char"}
, strikeout: {value: 1, offset: 22, size: "Char"} }

  hPrevFont := this._SendMessage(WM_GETFONT)
  VarSetCapacity(LOGFONT, szLF, 0)
  DllCall("GetObject", Ptr, hPrevFont, Int, szLF, Ptr, &LOGFONT)
  DllCall("DeleteObject", Ptr, hPrevFont)
  
  if this.FontSize  {
     hDC := DllCall("GetDC", Ptr, this.hwnd, Ptr)
     height := -DllCall("MulDiv", Int, this.FontSize, Int, DllCall("GetDeviceCaps", Ptr, hDC, Int, LOGPIXELSY), Int, 72)
     DllCall("ReleaseDC", Ptr, this.hwnd, Ptr, hDC)
     NumPut(height, LOGFONT, "Int")
  }
  FontStyle := this.FontStyle
  Loop, parse, FontStyle, %A_Space%
     if obj := styles[A_LoopField]
        NumPut(obj.value, &LOGFONT + obj.offset, obj.size)

  (this.FontSize > 24 && NumPut(ANTIALIASED_QUALITY, &LOGFONT + 26, "Char"))
  this.FontName && StrPut(this.FontName, &LOGFONT + 28, StrLen(this.FontName) * mult, A_IsUnicode ? "UTF-16" : "CP0")
  this.hFont := DllCall("CreateFontIndirect", Ptr, &LOGFONT, Ptr)

}

_SetColor() {
static WM_USER := 0x400, TTM_SETTIPBKCOLOR := WM_USER + 19, TTM_SETTIPTEXTCOLOR := WM_USER + 20

  VarSetCapacity(empty, 2, 0)
  DllCall("UxTheme\SetWindowTheme", Ptr, this.hwnd, Ptr, 0, Ptr, &empty)   
  ( this.TextColor != "" && this._SendMessage(TTM_SETTIPTEXTCOLOR, this._GetColor(this.TextColor)) )
  ( this.BackColor != "" && this._SendMessage(TTM_SETTIPBKCOLOR, this._GetColor(this.BackColor)) )

}

_GetColor(color) {
static WS_CHILD := 0x40000000, WM_CTLCOLORSTATIC := 0x138

  Gui, New, +hwndhGui +%WS_CHILD%
  Gui, Color, % color + 0 = "" ? color : Format("{:x}", color)
  Gui, Add, Text, hwndhText
  hdc := DllCall("GetDC", Ptr, hText, Ptr)
  SendMessage, WM_CTLCOLORSTATIC, hdc, hText,, ahk_id %hGui%
  clr := DllCall("GetBkColor", Ptr, hdc)
  DllCall("ReleaseDC", Ptr, hText, Ptr, hdc)
  Gui, Destroy
  Return clr

}

_SetInfo() {
static WM_USER := 0x400, TTM_SETMAXTIPWIDTH := WM_USER + 24
, TTM_ADDTOOL := WM_USER + (A_IsUnicode ? 50 : 4), WM_SETFONT := 0x30

  this._SendMessage(WM_SETFONT, this.hFont, 1)
  this._SendMessage(TTM_ADDTOOL, 0, this.pTI)
  this.SetTitle(this.icon, this.title)
  this._SendMessage(TTM_SETMAXTIPWIDTH, 0, A_ScreenWidth)
  this.SetText(this.text)

}

SetText(text) {
static WM_USER := 0x400, TTM_UPDATETIPTEXT := WM_USER + (A_IsUnicode ? 57 : 12)
NumPut(&text, this.pTI + (A_PtrSize = 4 ? 36 : 48))
this._SendMessage(TTM_UPDATETIPTEXT, 0, this.pTI)
}

SetTitle(icon := "", title := "") {
static WM_USER := 0x400, TTM_SETTITLE := WM_USER + (A_IsUnicode ? 33 : 32), TTM_UPDATE := WM_USER + 29

  ((icon || this.CloseButton) && title = "") && title := " "
  this._SendMessage(TTM_SETTITLE, icon, &title)
  (icon > 3 && DllCall("DestroyIcon", Ptr, icon))
  this._SendMessage(TTM_UPDATE)

}

Show(x := "", y := "") {
static WM_USER := 0x400, TTM_TRACKACTIVATE := WM_USER + 17, TTM_TRACKPOSITION := WM_USER + 18

  if this.TrayTip  {
     this._GetTrayIconCoords(xTT, yTT)
     if !this.TrayTimer  {
        timer := ObjBindMethod(this, "Show")
        SetTimer, % timer, 1000
        this.TrayTimer := timer
     }
     else  {
        this._CheckPosAboveTaskBar()
        if (this.xTT = xTT && this.yTT = yTT)
           Return
        else
           this.xTT := xTT, this.yTT := yTT
     }
  }
  else  {
     xTT := x, yTT := y
     (xTT = "" && xTT := this.x)
     (yTT = "" && yTT := this.y)
  
     if (xTT = "" || yTT = "") {
        CoordMode, Mouse
        MouseGetPos, xm, ym
        (xTT = "" && xTT := xm + 10)
        (yTT = "" && yTT := ym + 10)
     }
  }
  this._SendMessage(TTM_TRACKPOSITION, 0, xTT|(yTT<<16))
  this._SendMessage(TTM_TRACKACTIVATE, 1, this.pTI)
  
  if this.BalloonTip
     xMax := A_ScreenWidth, yMax := A_ScreenHeight
  else  {
     WinGetPos,,, W, H, % "ahk_id" this.hwnd
     xMax := A_ScreenWidth - W - 10
     yMax := A_ScreenHeight - H - 10
  }
  
  if (xTT > xMax || yTT > yMax)  {
     (xTT > xMax && xTT := xMax)
     (yTT > yMax && yTT := yMax)
     this._SendMessage(TTM_TRACKPOSITION, 0, xTT|(yTT<<16))
  }

}

Hide() {
static WM_USER := 0x400, TTM_TRACKACTIVATE := WM_USER + 17
this._SendMessage(TTM_TRACKACTIVATE, 0, this.pTI)
if timer := this.TrayTimer {
SetTimer, % timer, Off
this.TrayTimer := timer := ""
}
}

Destroy() {
if timer := this.TrayTimer {
SetTimer, % timer, Off
this.TrayTimer := timer := ""
}
this.__Delete()
this.SetCapacity(0)
this.base := ""
}

_CheckPosAboveTaskBar() {
static GW_HWNDNEXT := 2, SWP_NOSIZE := 1, SWP_NOMOVE := 2

  hTaskBar := WinExist("ahk_class Shell_TrayWnd")
  Loop
     hWnd := A_Index = 1 ? DllCall("GetTopWindow", Ptr, 0, Ptr) : DllCall("GetWindow", Ptr, hWnd, UInt, GW_HWNDNEXT, Ptr)
  until (hWnd = hTaskBar && TaskBarAbove := true) || hWnd = this.hwnd
  
  if TaskBarAbove
     DllCall("SetWindowPos", Ptr, this.hwnd, Ptr, 0
                           , Int, 0, Int, 0, Int, 0, Int, 0
                           , UInt, SWP_NOSIZE | SWP_NOMOVE )

}

_SendMessage(msg, wp := 0, lp := 0) {
Return DllCall("SendMessage", Ptr, this.hwnd, UInt, msg, Ptr, wp, Ptr, lp, Ptr)
}

_GetTrayIconCoords(ByRef x, ByRef y) {
static WM_USER := 0x400, TB_BUTTONCOUNT := WM_USER + 24, TB_GETBUTTON := WM_USER + 23, TB_GETITEMRECT := WM_USER + 29
, PtrSize := A_Is64bitOS ? 8 : 4, szTBBUTTON := 8 + PtrSize*3, szHWND := PtrSize

  for k, v in ["TrayNotifyWnd", "SysPager", "ToolbarWindow32"]
     hTray := DllCall("FindWindowEx", Ptr, k = 1 ? WinExist("ahk_class Shell_TrayWnd") : hTray, Ptr, 0, Str, v, UInt, 0, Ptr)
  WinWait, ahk_id %hTray%
  WinGet, PID, PID
  WinGetPos, xTB, yTB
  
  if !IsObject(RemoteBuff := new this.RemoteBuffer(PID, szTBBUTTON))  {
     x := xTB, y := yTB
     MsgBox, % "Не удалось создать удалённый буфер`nОшибка " A_LastError
     Return
  }
  SendMessage, TB_BUTTONCOUNT
  Loop % ErrorLevel  {
     SendMessage, TB_GETBUTTON, A_Index - 1, RemoteBuff.ptr
     pTBBUTTON := RemoteBuff.Read(szTBBUTTON)
     pHWND := RemoteBuff.Read(szHWND, NumGet(pTBBUTTON + 8 + PtrSize) - RemoteBuff.ptr)
     hWnd := NumGet(pHWND + 0, PtrSize = 4 ? "UInt" : "UInt64")
     if (hWnd = A_ScriptHwnd)  {
        SendMessage, TB_GETITEMRECT, A_Index - 1, RemoteBuff.ptr
        pRECT := RemoteBuff.Read(16)
        x := xTB + ( NumGet(pRECT + 0, "Int") + NumGet(pRECT + 8, "Int") )//2
        y := yTB + ( NumGet(pRECT + 4, "Int") + NumGet(pRECT + 12, "Int") )//2 - 5
        break
     }
  }
  RemoteBuff := "", ((x = "" || y = "") && (x := xTB, y := yTB))

}

__Delete() {
(this.hFont && DllCall("DeleteObject", Ptr, this.hFont))
(this.Icon > 3 && DllCall("DestroyIcon", Ptr, this.Icon))
DllCall("DestroyWindow", Ptr, this.hwnd)
}

class RemoteBuffer
{
__New(PID, size) {
static PROCESS_VM_OPERATION := 0x8, PROCESS_VM_WRITE := 0x20, PROCESS_VM_READ := 0x10
, MEM_COMMIT := 0x1000, PAGE_READWRITE := 0x4

     if !(this.hProc := DllCall("OpenProcess", UInt, PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE, Int, 0, UInt, PID, Ptr))
        Return
     
     if !(this.ptr := DllCall("VirtualAllocEx", UInt, this.hProc, UInt, 0, UInt, size, UInt, MEM_COMMIT, UInt, PAGE_READWRITE, Ptr))
        Return, "", DllCall("CloseHandle", Ptr, this.hProc)
  }
  
  __Delete()  {
     DllCall("VirtualFreeEx", Ptr, this.hProc, Ptr, this.ptr, UInt, 0, UInt, MEM_RELEASE := 0x8000)
     DllCall("CloseHandle", Ptr, this.hProc)
  }
  
  Read(size, offset = 0)  {
     static LocalBuff
     VarSetCapacity(LocalBuff, size, 0)
     if !DllCall("ReadProcessMemory", Ptr, this.hProc, Ptr, this.ptr + offset, Ptr, &LocalBuff, UInt, size, UInt, 0)
        Return, 0, DllCall("MessageBox", Ptr, 0, Str, "Не удалось прочитать данные`nОшибка """ A_LastError """", Str, "", UInt, 0)
     
     VarSetCapacity(LocalBuff, -1)
     Return &LocalBuff
  }

}
}

Timer(oTip) {
static i := 0
oTip.SetText(name "n" artist "n" vOutput1 (10 + ++i) "-" vOutput)
; oTip.SetText("Я цветной ToolTip!rnЯ исчезну через " (7 - ++i) " сек")
if (i = 7)
SetTimer,, Delete
}

CreateIconFromBase64(StringBASE64, Size)
{
StringBase64ToData(StringBASE64, IconData)
Return DllCall("CreateIconFromResourceEx", Ptr, &IconData + 4
, UInt, NumGet(&IconData, "UInt"), UInt, true, UInt, 0x30000, Int, Size, Int, Size, UInt, 0)
}

StringBase64ToData(StringBase64, ByRef OutData)
{
DllCall("Crypt32.dll\CryptStringToBinary", Ptr, &StringBase64
, UInt, StrLen(StringBase64), UInt, CRYPT_STRING_BASE64 := 1, UInt, 0, UIntP, Bytes, UIntP, 0, UIntP, 0)

VarSetCapacity(OutData, Bytes)
DllCall("Crypt32.dll\CryptStringToBinary", Ptr, &StringBase64
, UInt, StrLen(StringBase64), UInt, CRYPT_STRING_BASE64, Str, OutData, UIntP, Bytes, UIntP, 0, UIntP, 0)
Return Bytes
}

GetIcon() {
Icon32 =
(RTrim Join
qBAAACgAAAAgAAAAQAAAAAEAIAAAAAAAgBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
FRgjABMXIQATFyEAFBchABMXIQAVGCMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAABUYIwAAAAAAAAAAAAAAAAIAAAAAAAAAABUYIwAVGCMAExchABMXIRQUFyETExchABUYIwAVGCMAAAAAAAAAAAAAAAACAAAAAAAAAAAVGCMAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRgjABQXIgAUFyIAFBciABMXIQATFyEAFRgjABUYIwIUFyIAFBciqxQXIqoUFyIAFRgjAhUYIwAUFyIA
FBgiABQXIgATFyIAExciABUYIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVGCMAFBciABQXIhAUFiFCExgiABMWIQAAAAAA
FRgjAxUYIwAVFiO4ExghuBUYIwAVGCMDAAAAABQXIgATGiIAExchQhMYIhATGCIAFRgjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAABUYIwEUFyIAFBchMhQXIv8TFyFDFBciABUXIwIVGCMCFRgjABUXI7sUGCK7FRgjABUYIwIUFyMCFBciABQXIkMUFyL/FBchMhQXIgAVGCMBAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRgjABUXIgEVFyIAFBciuRQXItoVGSICFRgiARUYIwMUFyIBFBcinRMXIp0UFyIBFRgjAxUYIgEVGCIC
FBci2hQXIroVFyIAFRciARUYIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFRgjAAAAAAAAAAAAAAAAAgAAAAEVGCMAFBgjARQXIgAUFyIkFBci/xQXImEUFyIC
FRgjAAwOHwD/AAAA/wAAAAoRHQAVGCMAFBciAhQXImAUFyL/FBYiJRQXIgAUGCMBFBgiAAAAAAEAAAACAAAAAAAAAAAVGCMAAAAAAAAAAAAVGCMAFBUiABMVIgAUFyIA
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
+MADH/HgB4/38A/v///////P8///nnn//759///+f////n////////////8=
)
Return Icon32
}

;### Tray icon Menu Changes ###
menu, tray, add, Reload This Script, ReloadThisScript
return

			ReloadThisScript:
			Reload
			Sleep 1000 ; If successful, the reload will close this instance during the Sleep, so the line below will never be reached.
			MsgBox, 4,, The script could not be reloaded and will need to be manually restarted. Would you like Exit?
			IfMsgBox, Yes, ExitApp
			return`

@walbjorn
Copy link

walbjorn commented Feb 5, 2022

@master-sabi I have no idea why that would be. The script seems fine to me. But here are three suggestions:

I believe the script only works with the desktop Spotify application and not the Microsoft Store app. Make sure you're using the right one.

The script only works, for me, when Spotify is not minimized. So whenever I am using another application I alt+tab instead of minimizing Spotify.

In case you're using the Autohotkey beta you might want to try using the regular version.

Good luck!

@master-sabi
Copy link

master-sabi commented Feb 6, 2022

im still not sure what the problem would be. im trying to make my numberpad key 7 as volume down and numberpad key 9 as volume up
Capture
i want to control that volume.

@walbjorn
Copy link

walbjorn commented Feb 6, 2022

@master-sabi Do any other spotify hotkeys that you have set up work? Consider that the numpad keys have different 'names' depending on if NumLock is turned on or off which means that "Numpad0" will be "NumpadIns" if NumLock is turned off, etcetera.

Also I should note that the hotkeys set up through autohotkey only works, for me, when Spotify is not the active window (i.e. only works when Spotify is in the background).

@RadekPilich
Copy link

Just spent most of my workday troubleshooting this thing....

Guys, if the shortcut keys don't work reliably, you have to rewrite the script.

From:
spotifyKey("^{Right}")
To:
spotifyKey("{Ctrl Down}{Right}{Ctrl Up}")

@skyfenton
Copy link

Just spent most of my workday troubleshooting this thing....

Guys, if the shortcut keys don't work reliably, you have to rewrite the script.

From: spotifyKey("^{Right}") To: spotifyKey("{Ctrl Down}{Right}{Ctrl Up}")

I can't get this to work and I feel like I've read the whole thread, any ideas?

@The-Dolphi
Copy link

Works perfectly, thank you for it!
Just changed the volume control to page up and down with a #IfWinExist condition looking for Spotify.

@juanmalunatic
Copy link

juanmalunatic commented May 17, 2022

Modified some of the base functions to make it work with AHK v2

;; Get the handle
getSpotifyHwnd() {
	spotifyHwnd := WinGetID("ahk_exe Spotify.exe")
	Return spotifyHwnd
}

; Send a key, generic
spotifyKey(key) {
	spotifyHwnd := getSpotifyHwnd()
	; Chromium ignores keys when it isn't focused.
	; Focus the document window without bringing the app to the foreground.
	ControlFocus "Chrome_RenderWidgetHostHWND1", "ahk_id " . spotifyHwnd
	ControlSend key, , "ahk_id " . spotifyHwnd
	Return
}

; My combination, feel free to change to suit your tastes.
^+Space::{
        spotifyKey("{Space}")
        Return
}

@elhertz
Copy link

elhertz commented Jun 3, 2022

is there a way to show the spotify overlay? it doesn't show when using these hotkeys

@AdasOsorus
Copy link

Modified some of the base functions to make it work with AHK v2

;; Get the handle
getSpotifyHwnd() {
	spotifyHwnd := WinGetID("ahk_exe Spotify.exe")
	Return spotifyHwnd
}

; Send a key, generic
spotifyKey(key) {
	spotifyHwnd := getSpotifyHwnd()
	; Chromium ignores keys when it isn't focused.
	; Focus the document window without bringing the app to the foreground.
	ControlFocus "Chrome_RenderWidgetHostHWND1", "ahk_id " . spotifyHwnd
	ControlSend key, , "ahk_id " . spotifyHwnd
	Return
}

; My combination, feel free to change to suit your tastes.
^+Space::{
        spotifyKey("{Space}")
        Return
}

Thank you!

@walbjorn
Copy link

walbjorn commented Apr 19, 2023

is there a way to show the spotify overlay? it doesn't show when using these hotkeys

I use a combination of the solution in this gist + regular Media Key buttons for Play, Previous & next (see below). Overlay works for me this way.

; Win+Space: Play/Pause
#Space::
Send, {Media_Play_Pause}
Return

; Win+X: Previous
#X::
Send, {Media_Prev}
Return

; Win+C: Next
#C::
Send, {Media_Next}
Return

@Mane660
Copy link

Mane660 commented Apr 28, 2024

Modified some of the base functions to make it work with AHK v2

;; Get the handle
getSpotifyHwnd() {
	spotifyHwnd := WinGetID("ahk_exe Spotify.exe")
	Return spotifyHwnd
}

; Send a key, generic
spotifyKey(key) {
	spotifyHwnd := getSpotifyHwnd()
	; Chromium ignores keys when it isn't focused.
	; Focus the document window without bringing the app to the foreground.
	ControlFocus "Chrome_RenderWidgetHostHWND1", "ahk_id " . spotifyHwnd
	ControlSend key, , "ahk_id " . spotifyHwnd
	Return
}

; My combination, feel free to change to suit your tastes.
^+Space::{
        spotifyKey("{Space}")
        Return
}

Not working when spotify is minimized.
image

when spotify is opened in background it works fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment