/* MusicInfo plugin for gaim Displays whatever is currently playing in Winamp in the user's profile in Gaim Written by Reuben Balik Copyright 2005, 2006 By Reuben Balik Please ask permission before using any of this code for any other project/product. Last Update: March 1, 2006 */ #include "musicinfo.h" #include "internal.h" #include "status.h" #include "connection.h" #include "signals.h" #include "version.h" #include "notify.h" #include "savedstatuses.h" #include #include static const int maxLen = 2048; //the maximum profile length (I'm not actually sure what the official max AIM profile size is) static const int namesize = 256; //string size for artist and title static const int interval = 15 * 1000; //how often to check the winamp window (milleseconds) //these are all the strings that the plugin searches for static char artist_replace[] = "%ar%"; static char title_replace[] = "%so%"; static char start_songText[] = "(mi)"; static char end_songText[] = "(/mi)"; static char start_noSongText[] = "(ns)"; static char end_noSongText[] = "(/ns)"; static int timeoutint = 0; static GaimConnection* curgc; static char* prevInfo; static char* prevStat; // getSong gets the name of the song and artist from the title of the winamp/foobar window // and puts them in the buffers pointed to by title and artist // len is the size of the title and artist buffers // getSong returns FALSE if Winamp/foobar is closed or not playing, TRUE if it successfully got a song static gboolean getSong(char* title, char* artist, int len) { //get handle to winamp window HWND hwndPlayer = FindWindow("Winamp v1.x",NULL); char this_title[len*2]; int ret = GetWindowText(hwndPlayer,this_title,sizeof(this_title)); gboolean isfoo = FALSE; if(ret == 0) //let's check for foobar if we don't find winamp { hwndPlayer = FindWindow("{DA7CD0DE-1602-45e6-89A1-C2CA151E008E}", NULL); ret = GetWindowText(hwndPlayer,this_title,sizeof(this_title)); if(ret != 0) { if(g_strstr_len(this_title, len*2, "[") != g_strrstr_len(this_title, len*2, "[")) hideBetween(this_title, "[", "]", len*2); isfoo = TRUE; } } if(ret != 0) { if(strstr(this_title, "[Stopped]") != NULL) return FALSE; if(strstr(this_title, "[Paused]") != NULL) return FALSE; if(isfoo && strstr(this_title, " ") == NULL) //this is the only string i could find that is constant when foobar plays return FALSE; char temp[len]; char *firsthyphen, *secondhyphen, *period, *ptr; firsthyphen = g_strstr_len(this_title, len, " - "); if(firsthyphen == NULL && !isfoo) //when winamp starts up without playing it doesn't display the song name return FALSE; secondhyphen = g_strrstr_len(this_title, len, " - "); if(isfoo) ptr = this_title; else { period = g_strstr_len(this_title, len, "."); ptr = period+2; } int count = 0; if(firsthyphen != secondhyphen || (isfoo && firsthyphen != NULL)) //we have an artist { while(ptr != firsthyphen && count < len) { temp[count] = *ptr; ptr++; count++; } temp[count] = '\0'; ptr+=3; strcpy(artist, temp); } else //no artist { strcpy(artist, "Unknown"); } count=0; while(ptr != secondhyphen && *ptr != '[' && count < len) { temp[count] = *ptr; ptr++; count++; } if(isfoo) count = count - 5; temp[count] = '\0'; strncpy(title, temp, count+1); return TRUE; } else { return FALSE; } } // replaces the first instance of replaceStr in text with replaceWith // max is the size of text static gboolean replaceText(char* text, char* replaceStr, char* replaceWith, int max) { char* pos = text; char* foundAt = strstr(text, replaceStr); char newString[max]; if(foundAt == NULL) return FALSE; int cur = 0; while(pos != foundAt && cur < max) { newString[cur] = *pos; pos++; cur++; } char* replacePos = replaceWith; while(*replacePos != '\0' && cur < max) { newString[cur] = *replacePos; replacePos++; cur++; } pos+=strlen(replaceStr); while(*pos != '\0' && cur < max) { newString[cur] = *pos; pos++; cur++; } newString[cur] = '\0'; strncpy(text, newString, max); return TRUE; } //hides all text from startReplace to endReplace with a max string size of len static void hideBetween(char* text, char* startReplace, char* endReplace, int len) { char* hidepos = strstr(text, startReplace); char* endhidepos = strstr(text, endReplace); int noOverFlowHere = strlen(endReplace); endhidepos += noOverFlowHere; char* pos = text; char newString[len]; if(hidepos != NULL && endhidepos != NULL && endhidepos > hidepos) { int n = 0; while(pos != hidepos) { newString[n] = *pos; n++; pos++; } pos = endhidepos; while(*pos != '\0' && n + noOverFlowHere < len) { newString[n] = *pos; n++; pos++; } newString[n] = '\0'; strncpy(text, newString, len); } } //gets the text of whatever the user sets the status to. (not necessarily what status is on the server) static gboolean getStatusText(char* text, GaimAccount *account, int len) { GaimStatus* status; GaimSavedStatus* savedStatus; gboolean goodToUse = FALSE; GaimStatusPrimitive prim = GAIM_STATUS_UNSET; status = gaim_account_get_active_status(account); if(status != NULL) { if(gaim_status_is_available(status)) { goodToUse = TRUE; } else { prim = gaim_status_type_get_primitive(gaim_status_get_type(status)); if(prim == GAIM_STATUS_AWAY) goodToUse = TRUE; } } if(!goodToUse) return FALSE; savedStatus = gaim_savedstatus_get_current(); if(savedStatus != NULL && gaim_savedstatus_get_message(savedStatus) != NULL) { strncpy(text, gaim_savedstatus_get_message(savedStatus), len); return TRUE; } else return FALSE; } // update the info for the connection gc static void updateInfo(GaimConnection *gc) { GaimAccount* account; account = gaim_connection_get_account(gc); char curAcct[maxLen]; char statusText[maxLen]; char title[namesize]; char artist[namesize]; const char* sid = NULL; if(gaim_account_get_user_info(account) != NULL) g_strlcpy(curAcct, gaim_account_get_user_info(account), sizeof(curAcct)); else curAcct[0] = '\0'; gboolean hasStatus = getStatusText(statusText, account, maxLen); //had to do this because i couldn't get it to pass back through the getStatusText function sid = gaim_status_get_id(gaim_account_get_active_status(account)); if(getSong(title, artist, namesize)) { //replace the appropriate text replaceText(curAcct, start_songText, "\0", maxLen); replaceText(curAcct, end_songText, "\0", maxLen); replaceText(curAcct, artist_replace, artist, maxLen); replaceText(curAcct, title_replace, title, maxLen); //hide everthing in the "no song" tag hideBetween(curAcct, start_noSongText, end_noSongText, maxLen); //and do it for the status if(hasStatus) { replaceText(statusText, start_songText, "\0", maxLen); replaceText(statusText, end_songText, "\0", maxLen); replaceText(statusText, artist_replace, artist, maxLen); replaceText(statusText, title_replace, title, maxLen); hideBetween(statusText, start_noSongText, end_noSongText, maxLen); } } else { //replace the appropriate text replaceText(curAcct, start_noSongText, "\0", maxLen); replaceText(curAcct, end_noSongText, "\0", maxLen); //hide everything in the song tag hideBetween(curAcct, start_songText, end_songText, maxLen); //and for the status: if(hasStatus) { replaceText(statusText, start_noSongText, "\0", maxLen); replaceText(statusText, end_noSongText, "\0", maxLen); hideBetween(statusText, start_songText, end_songText, maxLen); } } //this is so we don't update the server if we don't have to if(strcmp(curAcct, prevInfo) != 0) { serv_set_info(gc, curAcct); strncpy(prevInfo, curAcct, maxLen); } if(hasStatus && strcmp(statusText, prevStat) != 0) { gaim_account_set_status(account, sid, TRUE, "message", statusText, NULL); strncpy(prevStat, statusText, maxLen); } } static gboolean callUpdateInfo(gpointer gc) { updateInfo((GaimConnection*) gc); return TRUE; } //this is only called for the first update. it has to return FALSE to kill the timer //and reset prevInfo static gboolean callFirstUpdate(gpointer gc) { *prevInfo = '\0'; *prevStat = '\0'; updateInfo((GaimConnection*) gc); return FALSE; } static void online(GaimConnection *gc) { //doesn't run on two connections at once at the moment if(timeoutint == 0) { curgc = gc; //a slight delay is needed for the first update because gaim already updates the server when you sign on g_timeout_add(2200, callFirstUpdate, (gpointer)gc); timeoutint = g_timeout_add(interval, callUpdateInfo, (gpointer)gc); } } static void signoff(GaimConnection *gc) { //this should only remove if the account signing off is the one that we're updating if(gc == curgc) { g_source_remove(timeoutint); timeoutint = 0; } } static gboolean plugin_load(GaimPlugin *plugin) { prevInfo = (char*) malloc(sizeof(char) * maxLen); *prevInfo = '\0'; prevStat = (char*) malloc(sizeof(char) * maxLen); *prevStat = '\0'; gaim_signal_connect(gaim_connections_get_handle(), "signed-on", plugin, GAIM_CALLBACK(online), NULL); gaim_signal_connect(gaim_connections_get_handle(), "signing-off", plugin, GAIM_CALLBACK(signoff), NULL); return TRUE; } static gboolean plugin_unload(GaimPlugin *plugin) { free(prevInfo); free(prevStat); gaim_signal_disconnect(gaim_connections_get_handle(), "signed-on", plugin, GAIM_CALLBACK(online)); gaim_signal_disconnect(gaim_connections_get_handle(), "signing-off", plugin, GAIM_CALLBACK(signoff)); return TRUE; } static GaimPluginInfo info = { GAIM_PLUGIN_MAGIC, GAIM_MAJOR_VERSION, GAIM_MINOR_VERSION, GAIM_PLUGIN_STANDARD, /**< type */ NULL, /**< ui_requirement */ 0, /**< flags */ NULL, /**< dependencies */ GAIM_PRIORITY_DEFAULT, /**< priority */ "core-cybrguyrsb-musicinfo", /**< id */ N_("MusicInfo"), /**< name */ "0.4", /**< version */ /** summary */ N_("Displays the current song in Winamp in your user info"), /** description */ N_("Displays the current song in Winamp in your user info"), "Reuben Balik ", /**< author */ "http://www.ews.uiuc.edu/~rbalik2/musicinfo/", /**< homepage */ plugin_load, /**< load */ plugin_unload, /**< unload */ NULL, /**< destroy */ NULL, /**< ui_info */ NULL, /**< extra_info */ NULL, /**< prefs_info */ NULL }; static void init_plugin(GaimPlugin *plugin) { } GAIM_INIT_PLUGIN(mig, init_plugin, info)