Last active
May 18, 2021 21:12
-
-
Save orange-in-space/35b32b625721b218703af89ec8a1e9ac to your computer and use it in GitHub Desktop.
Spotify Windows Desktop Client Song Title Sniffer Library!>< (SpotifyのWindowsデスクトップ版のウィンドウタイトルから曲名とか取得するライブラリ><(フックして変化受け取る方式))
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Runtime.InteropServices; | |
using System.Text; | |
namespace Orange.SpotifyTools | |
{ | |
// | |
// orange><'s | |
// Spotify Windows Desktop Client Song Title Sniffer | |
// | |
// Copyrignt (c) orange_in_space, 2018 | |
// License: MIT | |
// | |
// | |
// HowtoUse | |
#region exsample | |
class HowtoUse | |
{ | |
public void ExampleMain() | |
{ | |
SpotifyDesktopClientSongTitleSniffer sniffer; | |
//Hook, Start automatically in the constructor | |
sniffer = new SpotifyDesktopClientSongTitleSniffer(); | |
try | |
{ | |
sniffer.OnUpdateSongTitle += Sniffer_OnUpdateSongTitle; | |
sniffer.OnPlayerStoppedOrAd += Sniffer_OnPlayerStoppedOrAd; | |
//manually check for 1st time | |
sniffer.CheckAndSendSongInfo(); | |
System.Windows.Forms.MessageBox.Show("close message box to exit."); | |
} | |
finally | |
{ | |
sniffer.Dispose(); | |
} | |
} | |
private void Sniffer_OnUpdateSongTitle(SongInformation args) | |
{ | |
Console.WriteLine($"Title: {args.Title}"); | |
Console.WriteLine($"Artist: {args.Artist}"); | |
} | |
private void Sniffer_OnPlayerStoppedOrAd() | |
{ | |
Console.WriteLine("Stop or Ad"); | |
} | |
} | |
#endregion | |
// | |
// end of HowtoUse | |
public struct SongInformation | |
{ | |
public string Title; | |
public string Artist; | |
public string RawText; | |
} | |
public class SpotifyDesktopClientSongTitleSniffer : IDisposable | |
{ | |
public delegate void UpdateSongTitle(SongInformation args); | |
public delegate void PlayerStoppedOrAd(); | |
private WinEventDelegate procDelegate; | |
public event UpdateSongTitle OnUpdateSongTitle; | |
public event PlayerStoppedOrAd OnPlayerStoppedOrAd; | |
IntPtr hhook_namechange; | |
// constructor | |
public SpotifyDesktopClientSongTitleSniffer() | |
{ | |
//hook | |
procDelegate = new WinEventDelegate(WinEventProc); | |
hhook_namechange = SetWinEventHook(EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, IntPtr.Zero, | |
procDelegate, 0, 0, WINEVENT_OUTOFCONTEXT); | |
} | |
#region IDisposable Support | |
private bool disposedValue = false; // 重複する呼び出しを検出するには | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (!disposedValue) | |
{ | |
if (disposing) | |
{ | |
// TODO: マネージ状態を破棄します (マネージ オブジェクト)。 | |
} | |
// TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下のファイナライザーをオーバーライドします。 | |
// TODO: 大きなフィールドを null に設定します。 | |
UnhookWinEvent(hhook_namechange); | |
disposedValue = true; | |
} | |
} | |
// TODO: 上の Dispose(bool disposing) にアンマネージ リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします。 | |
~SpotifyDesktopClientSongTitleSniffer() | |
{ | |
// このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。 | |
Dispose(false); | |
} | |
// このコードは、破棄可能なパターンを正しく実装できるように追加されました。 | |
public void Dispose() | |
{ | |
// このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。 | |
Dispose(true); | |
// TODO: 上のファイナライザーがオーバーライドされる場合は、次の行のコメントを解除してください。 | |
GC.SuppressFinalize(this); | |
} | |
#endregion | |
/// <summary> | |
/// check for first time. | |
/// </summary> | |
public void CheckAndSendSongInfo() | |
{ | |
IEnumerable<Process> SpotifyProcesses = from p in Process.GetProcesses() where p.ProcessName == @"Spotify" select p; | |
if (SpotifyProcesses.Count() > 0) | |
{ | |
string windowtext = SpotifyProcesses.First().MainWindowTitle; | |
SendSongInfo(windowtext); | |
} | |
else | |
{ | |
SendStoppedOrAd(); | |
} | |
return; | |
} | |
// oh... wasn't needed this ><; | |
public void CheckAndSendSongInfoUnmanaged() | |
{ | |
IntPtr hwnd; | |
if (FindSpotifyWindow(out hwnd)) | |
{ | |
string windowtext = GetWindowText(hwnd); | |
SendSongInfo(windowtext); | |
} | |
else | |
{ | |
SendStoppedOrAd(); | |
} | |
} | |
private void SendSongInfo(string rawText) | |
{ | |
string separator = @" - "; | |
int splitposition = rawText.IndexOf(separator); | |
if (splitposition > 0)// include ( != "Spotify" ) !>< | |
{ | |
SongInformation info; | |
info.Artist = rawText.Substring(0, splitposition); | |
info.Title = rawText.Substring(splitposition + 3); | |
info.RawText = rawText; | |
OnUpdateSongTitle(info); | |
} | |
else | |
{ | |
OnPlayerStoppedOrAd(); | |
} | |
} | |
private void SendStoppedOrAd() | |
{ | |
OnPlayerStoppedOrAd(); | |
} | |
private bool FindSpotifyWindow(out IntPtr hwnd) | |
{ | |
IEnumerable<Process> SpotifyProcesses = from p in Process.GetProcesses() where p.ProcessName == @"Spotify" select p; | |
if (SpotifyProcesses.Count() > 0) | |
{ | |
hwnd = SpotifyProcesses.First().MainWindowHandle; | |
return true; | |
} | |
else | |
{ | |
hwnd = IntPtr.Zero; | |
return false; | |
} | |
} | |
private string GetWindowText(IntPtr hwnd) | |
{ | |
int textLen = GetWindowTextLength(hwnd); | |
StringBuilder sb = new StringBuilder(textLen + 1); | |
GetWindowText(hwnd, sb, sb.Capacity); | |
return sb.ToString(); | |
} | |
private string GetClassName(IntPtr hwnd) | |
{ | |
StringBuilder sb = new StringBuilder(256); | |
GetClassName(hwnd, sb, sb.Capacity); | |
return sb.ToString(); | |
} | |
private void WinEventProc(IntPtr hWinEventHook, uint eventType, | |
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) | |
{ | |
if (idObject != 0 || idChild != 0) | |
{ | |
return; | |
} | |
//check has windowtext | |
int windowtextlength = GetWindowTextLength(hwnd); | |
if (0 < windowtextlength) | |
{ | |
string windowtext = GetWindowText(hwnd); | |
string classname = GetClassName(hwnd); | |
//check Electron window | |
if (classname == @"Chrome_WidgetWin_0") | |
{ | |
uint processId; | |
GetWindowThreadProcessId(hwnd, out processId); | |
int pid = unchecked((int)processId); | |
using (Process p_obj = Process.GetProcessById(pid)) | |
{ | |
if (p_obj.ProcessName == @"Spotify") | |
{ | |
SendSongInfo(windowtext); | |
} | |
} | |
} | |
} | |
} | |
// | |
#region win32api | |
private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, | |
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); | |
[DllImport("user32.dll")] | |
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr | |
hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, | |
uint idThread, uint dwFlags); | |
[DllImport("user32.dll")] | |
private static extern bool UnhookWinEvent(IntPtr hWinEventHook); | |
// Constants from winuser.h | |
private const uint WINEVENT_OUTOFCONTEXT = 0; | |
private const uint EVENT_OBJECT_NAMECHANGE = 0x800C; | |
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern int GetWindowText(IntPtr hWnd, | |
StringBuilder lpString, int nMaxCount); | |
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern int GetWindowTextLength(IntPtr hWnd); | |
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern int GetClassName(IntPtr hWnd, | |
StringBuilder lpClassName, int nMaxCount); | |
[DllImport("user32.dll", SetLastError = true)] | |
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment