Skip to content

Instantly share code, notes, and snippets.

@jcsteh
Last active June 21, 2024 14:44
Show Gist options
  • 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
}
@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.

@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`

@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.

@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!

@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.

@walbjorn
Copy link

walbjorn commented May 7, 2024

This is what currently works for me using AHK 1.1 (it will not work if you minimize Spotify).

The keybindings are set at the bottom of the script, currently set to use Winkey and Winkey+Alt modifiers.

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()
  ControlClick, x500 y100, ahk_id %spotifyHwnd%, , Left, 1, U
  ControlSend, ahk_parent, %key%, ahk_id %spotifyHwnd%
}

; Define keybindings here
#x::spotifyKey("^{Left}")			; Previous song
#c::spotifyKey("^{Right}")		; Next song
#Down::spotifyKey("^{Down}")	; Volume down
#Up::spotifyKey("^{Up}")		; Volume up
#Space::spotifyKey("{Space}")		; Play/pause
;#!Space::spotifyKey("!+{b}")		; Like song
#Left::spotifyKey("+{Left}")		; Seek backward
#Right::spotifyKey("+{Right}")	; Seek forward

; Win+Alt+Space: Hotkey to show/focus Spotify
#!Space::
  WinActivate, ahk_exe spotify.exe
  WinMaximize, ahk_exe spotify.exe
  return

@OmTatSat
Copy link

OmTatSat commented Jun 13, 2024

to make the controls work when the window is minimized to the taskbar or tray, you need to use winhide

can be done like this

#If OnMy_Close_or_Min_Buttons()
LButton:: Winhide
#If
OnMy_Close_or_Min_Buttons() {
Static MyWindowCriteria := "ahk_exe Spotify.exe" ; change to meet your neeeds
Critical
SetTitleMatchMode, 2
CoordMode, Mouse, Screen
MouseGetPos, X, Y, MID
;ToolTip, mid %mid%
If (WID := WinExist(MyWindowCriteria)) && (WID = MID) {
SendMessage, 0x0084, 0, (X & 0xFFFF) | ((Y & 0xFFFF) << 16) ; WM_NCHITTEST
;Return (ErrorLevel = 20) ; HTCLOSE
;Return (ErrorLevel = 8) ; HTmin
;ToolTip, ErrorLevel %ErrorLevel%
if ((ErrorLevel = "20") or (ErrorLevel = "8"))
{
;ToolTip, test
return true
}
}
Return False
}

and function to send hotkeys is like this

; 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
{
DetectHiddenWindows, on
DetectHiddenText, on
WinGet, OutputVar1, ID , ahk_exe Spotify.exe
ControlFocus, , ahk_id %OutputVar1%
ControlSend, ahk_parent , %key%, ahk_class Chrome_WidgetWin_1
return
}
IfWinActive, ahk_exe Spotify.exe
{
Send, %key%
;ToolTip, keyssend
return
}
Return
}

@Zivers88
Copy link

Zivers88 commented Jun 17, 2024

to make the controls work when the window is minimized to the taskbar or tray, you need to use winhide

can be done like this

#If OnMy_Close_or_Min_Buttons() LButton:: Winhide #If OnMy_Close_or_Min_Buttons() { Static MyWindowCriteria := "ahk_exe Spotify.exe" ; change to meet your neeeds Critical SetTitleMatchMode, 2 CoordMode, Mouse, Screen MouseGetPos, X, Y, MID ;ToolTip, mid %mid% If (WID := WinExist(MyWindowCriteria)) && (WID = MID) { SendMessage, 0x0084, 0, (X & 0xFFFF) | ((Y & 0xFFFF) << 16) ; WM_NCHITTEST ;Return (ErrorLevel = 20) ; HTCLOSE ;Return (ErrorLevel = 8) ; HTmin ;ToolTip, ErrorLevel %ErrorLevel% if ((ErrorLevel = "20") or (ErrorLevel = "8")) { ;ToolTip, test return true } } Return False }

and function to send hotkeys is like this

; 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 { DetectHiddenWindows, on DetectHiddenText, on WinGet, OutputVar1, ID , ahk_exe Spotify.exe ControlFocus, , ahk_id %OutputVar1% ControlSend, ahk_parent , %key%, ahk_class Chrome_WidgetWin_1 return } IfWinActive, ahk_exe Spotify.exe { Send, %key% ;ToolTip, keyssend return } Return }

Where should I paste this code? I get an error
«Error at line 23.
Line Text: and function to send hotkeys is like this
Error: This line does not contain a recognized action.»

The code above from walbjorn works great!
(and the page author's code does not work for me) It turned out to work, but somehow strangely and every once in a while

@OmTatSat
Copy link

OmTatSat commented Jun 17, 2024

what is in line 23?
you can insert this part at the beginning or end of the code

#If OnMy_Close_or_Min_Buttons()
LButton:: Winhide
#If
OnMy_Close_or_Min_Buttons() {
Static MyWindowCriteria := "ahk_exe Spotify.exe" ; change to meet your neeeds
Critical
SetTitleMatchMode, 2
CoordMode, Mouse, Screen
MouseGetPos, X, Y, MID
;ToolTip, mid %mid%
If (WID := WinExist(MyWindowCriteria)) && (WID = MID) {
SendMessage, 0x0084, 0, (X & 0xFFFF) | ((Y & 0xFFFF) << 16) ; WM_NCHITTEST
;Return (ErrorLevel = 20) ; HTCLOSE
;Return (ErrorLevel = 8) ; HTmin
;ToolTip, ErrorLevel %ErrorLevel%
if ((ErrorLevel = "20") or (ErrorLevel = "8"))
{
;ToolTip, test
return true
}
}
Return False
}

this

; 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
{
DetectHiddenWindows, on
DetectHiddenText, on
WinGet, OutputVar1, ID , ahk_exe Spotify.exe
ControlFocus, , ahk_id %OutputVar1%
ControlSend, ahk_parent , %key%, ahk_class Chrome_WidgetWin_1
return
}
IfWinActive, ahk_exe Spotify.exe
{
Send, %key%
;ToolTip, keyssend
return
}
Return
}`

instead of

`; Send a key to Spotify.
spotifyKey(key) {
spotifyHwnd := getSpotifyHwnd()
ControlClick, x500 y100, ahk_id %spotifyHwnd%, , Left, 1, U
ControlSend, ahk_parent, %key%, ahk_id %spotifyHwnd%
}

but additional testing revealed that sometimes this method stops working, I am looking for and trying new approaches that can give permanent work in a collapsed state.

@drandarov-io
Copy link

drandarov-io commented Jun 18, 2024

https://gist.github.com/drandarov-io/96a5df7d1dee2d545f22fcaddf522284

Thought I'd add something that worked for me after much trial and error.

@OmTatSat
Copy link

Yesterday I came to a similar approach) until it seems not stop working

spotifyKey(key) {
IfWinNotActive, ahk_exe Spotify.exe
{
DetectHiddenWindows, on
DetectHiddenText, on

	ControlFocus, , , Chrome Legacy Window
	ControlSend, ahk_parent , %key%, ahk_class Chrome_WidgetWin_1
	return
	}
IfWinActive, ahk_exe Spotify.exe
{
	Send,  %key%
	return
}
Return

}

@Zivers88
Copy link

Zivers88 commented Jun 21, 2024

what is in line 23? you can insert this part at the beginning or end of the code

#If OnMy_Close_or_Min_Buttons() LButton:: Winhide #If OnMy_Close_or_Min_Buttons() { Static MyWindowCriteria := "ahk_exe Spotify.exe" ; change to meet your neeeds Critical SetTitleMatchMode, 2 CoordMode, Mouse, Screen MouseGetPos, X, Y, MID ;ToolTip, mid %mid% If (WID := WinExist(MyWindowCriteria)) && (WID = MID) { SendMessage, 0x0084, 0, (X & 0xFFFF) | ((Y & 0xFFFF) << 16) ; WM_NCHITTEST ;Return (ErrorLevel = 20) ; HTCLOSE ;Return (ErrorLevel = 8) ; HTmin ;ToolTip, ErrorLevel %ErrorLevel% if ((ErrorLevel = "20") or (ErrorLevel = "8")) { ;ToolTip, test return true } } Return False }

this

; 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 { DetectHiddenWindows, on DetectHiddenText, on WinGet, OutputVar1, ID , ahk_exe Spotify.exe ControlFocus, , ahk_id %OutputVar1% ControlSend, ahk_parent , %key%, ahk_class Chrome_WidgetWin_1 return } IfWinActive, ahk_exe Spotify.exe { Send, %key% ;ToolTip, keyssend return } Return }`

instead of

`; Send a key to Spotify. spotifyKey(key) { spotifyHwnd := getSpotifyHwnd() ControlClick, x500 y100, ahk_id %spotifyHwnd%, , Left, 1, U ControlSend, ahk_parent, %key%, ahk_id %spotifyHwnd% }

but additional testing revealed that sometimes this method stops working, I am looking for and trying new approaches that can give permanent work in a collapsed state.

This code works fine for me with Spotify minimized in tray (!), much to my delight (finally! After months of agony!)

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
}

; Ctrl+Shift+q: Play/Pause
^+q::
{
spotifyKey("{Space}")
Return
}

; alt+v: Next
!v::
{
spotifyKey("^{Right}")
Return
}

; alt+z: Previous
!z::
{
spotifyKey("^{Left}")
Return
}

; Ctrl+alt+Up: Volume up
^!Up::
{
spotifyKey("^{Up}")
Return
}

; Ctrl+alt+Down: Volume Down
^!Down::
{
spotifyKey("^{Down}")
Return
}

; Ctrl+: Show Spotify ^::
{
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
}

Sometimes it doesn't catch the focus or on the contrary, holds it, but it already trifles for a couple clicks :)

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