Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save orange-in-space/35b32b625721b218703af89ec8a1e9ac to your computer and use it in GitHub Desktop.
Save orange-in-space/35b32b625721b218703af89ec8a1e9ac to your computer and use it in GitHub Desktop.
Spotify Windows Desktop Client Song Title Sniffer Library!>< (SpotifyのWindowsデスクトップ版のウィンドウタイトルから曲名とか取得するライブラリ><(フックして変化受け取る方式))
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