unit ml;
{ Media Library IPC message constants and records }
{ Written by Saivert <saivert@gmail.com> }
{ Homepage: [url]http://saivertweb.no-ip.com[/url] }
{ Original license from C header: }
(*
** Copyright (C) 2003 Nullsoft, Inc.
**
** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
** liable for any damages arising from the use of this software.
**
** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
** alter it and redistribute it freely, subject to the following restrictions:
**
** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
**
** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
**
** 3. This notice may not be removed or altered from any source distribution.
**
*)
interface
uses Windows, Messages;
const
MLHDR_VER = $15;
type
PwinampMediaLibraryPlugin = ^winampMediaLibraryPlugin;
winampMediaLibraryPlugin =
record
version: Integer;
description: PChar;
init:
function: Integer;
cdecl;
quit:
procedure;
cdecl;
{ return NONZERO if you accept this message as yours, otherwise 0 to pass it
to other plugins }
MessageProc:
function(message_type, param1, param2, param3: Integer): Integer;
cdecl;
{ all the following data is filled in by the library }
hwndWinampParent: HWND;
hwndLibraryParent: HWND;
{ send this any of the WM_ML_IPC messages }
hDllInstance: HINST;
end;
TwinampMediaLibraryPlugin = winampMediaLibraryPlugin;
{ messages your plugin may receive on MessageProc() }
const
ML_MSG_TREE_BEGIN = $100;
ML_MSG_TREE_ONCREATEVIEW = $100;
{ param1 = param of tree item,
param2 is HWND of parent.
return HWND if it is us }
ML_MSG_TREE_ONCLICK = $101;
{ param1 = param of tree item,
param2 = action type (below),
param3 = HWND of main window }
ML_ACTION_RCLICK = 0;
{ return value should be nonzero if ours }
ML_ACTION_DBLCLICK = 1;
ML_ACTION_ENTER = 2;
ML_MSG_TREE_ONDROPTARGET = $102;
{ param1 = param of tree item,
param2 = type of drop (ML_TYPE_*),
param3 = pointer to data (or NULL if
querying).
return -1 if not allowed, 1 if allowed,
or 0 if not our tree item }
ML_MSG_TREE_ONDRAG = $103;
{ param1 = param of tree item,
param2 = POINT * to the mouse position,
and param3 = (int *) to the type
set *(int*)param3 to the ML_TYPE you want
and return 1, if you support drag&drop,
or -1 to prevent d&d. }
ML_MSG_TREE_ONDROP = $104;
{ param1 = param of tree item,
param2 = POINT * to the mouse position
if you support dropping, send the appropriate
ML_IPC_HANDLEDROP using SendMessage() and
return 1, otherwise return -1. }
ML_MSG_TREE_END = $1FF;
{ end of tree specific messages }
ML_MSG_ONSENDTOBUILD = $300;
{ you get sent this when the sendto menu gets built }
{ param1 = type of source, param2 param to pass as context to ML_IPC_ADDTOSENDTO
be sure to return 0 to allow other plugins to add their context menus }
{ if your sendto item is selected, you will get this with your param3 == your user32 (preferably some
unique identifier (like your plugin message proc). See ML_IPC_ADDTOSENDTO }
ML_MSG_ONSENDTOSELECT = $301;
{ param1 = type of source, param2 = data, param3 = user32 }
{ return TRUE and do a config dialog using param1 as a HWND parent for this one }
ML_MSG_CONFIG = $400;
{ types for drag and drop }
ML_TYPE_ITEMRECORDLIST = 0;
{ if this, cast obj to itemRecordList }
ML_TYPE_FILENAMES = 1;
{ double NULL terminated char * to files, URLS, or playlists }
ML_TYPE_STREAMNAMES = 2;
{ double NULL terminated char * to URLS, or playlists ( effectively the same as ML_TYPE_FILENAMES, but not for files) }
ML_TYPE_CDTRACKS = 3;
{ if this, cast obj to itemRecordList (CD tracks) -- filenames should be cda://<drive letter>,<track index>. artist/album/title might be valid (CDDB) }
ML_TYPE_QUERYSTRING = 4;
{ char * to a query string }
ML_TYPE_TREEITEM = 69;
{ uhh? }
{ if you wish to put your tree items under "devices", use this constant for
ML_IPC_ADDTREEITEM }
ML_TREEVIEW_ID_DEVICES = 10000;
{ children communicate back to the media library by
SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,param,ML_IPC_X); }
WM_ML_IPC = WM_USER+$1000;
ML_IPC_ADDTREEITEM = $0101;
{ pass mlAddTreeItemStruct as the param }
ML_IPC_SETTREEITEM = $0102;
{ pass mlAddTreeItemStruct with this_id valid }
ML_IPC_DELTREEITEM = $0103;
{ pass param of tree item to remove }
ML_IPC_GETCURTREEITEM = $0104;
{ returns current tree item param or 0 if none }
ML_IPC_SETCURTREEITEM = $0105;
{ selects the tree item passed, returns 1 if found, 0 if not }
ML_IPC_GETTREE = $106;
{ returns a HMENU with all the tree items. the caller needs to delete the returned handle! pass mlGetTreeStruct as the param }
type
PmlGetTreeStruct = ^mlGetTreeStruct;
mlGetTreeStruct =
record
item_start: Integer;
{ TREE_PLAYLISTS, TREE_DEVICES... }
cmd_offset: Integer;
{ menu command offset if you need to make a command proxy, 0 otherwise }
max_numitems: Integer;
{ maximum number of items you wish to insert or -1 for no limit }
end;
TmlGetTreeStruct = mlGetTreeStruct;
const
ML_IPC_NEWPLAYLIST = $107;
{ pass hwnd for dialog parent as param }
ML_IPC_IMPORTPLAYLIST = $108;
{ pass hwnd for dialog parent as param }
ML_IPC_IMPORTCURRENTPLAYLIST = $109;
ML_IPC_GETPLAYLISTWND = $10A;
ML_IPC_SAVEPLAYLIST = $10B;
{ pass hwnd for dialog parent as param }
ML_IPC_OPENPREFS = $10C;
ML_IPC_PLAY_PLAYLIST = $010D;
{ plays the playlist pointed to by the tree item passed, returns 1 if found, 0 if not }
ML_IPC_LOAD_PLAYLIST = $010E;
{ loads the playlist pointed to by the tree item passed into the playlist editor, returns 1 if found, 0 if not }
type
PmlAddTreeItemStruct = ^mlAddTreeItemStruct;
mlAddTreeItemStruct =
record
parent_id: Integer;
{ 0=root, or ML_TREEVIEW_ID_* }
title: PChar;
has_children: Integer;
this_id: Integer;
{ filled in by the library on ML_IPC_ADDTREEITEM }
end;
TmlAddTreeItemStruct = mlAddTreeItemStruct;
const
ML_IPC_GETFILEINFO = $0200;
{ pass it a &itemRecord with a valid filename (and all other fields NULL), and it will try to fill in the rest }
ML_IPC_FREEFILEINFO = $0201;
{ pass it a &itemRecord that was filled by getfileinfo, it will free the strings it allocated }
ML_IPC_HANDLEDRAG = $0300;
{ pass it a &mlDropItemStruct it will handle cursors etc (unless flags has the lowest bit set), and it will set result appropriately: }
ML_IPC_HANDLEDROP = $0301;
{ pass it a &mlDropItemStruct with data on drop: }
ML_IPC_SENDTOWINAMP = $302;
{ send with a mlSendToWinampStruct: }
type
PmlSendToWinampStruct = ^mlSendToWinampStruct;
mlSendToWinampStruct =
record
_type: Integer;
{ ML_TYPE_ITEMRECORDLIST, etc }
data: Pointer;
{ object to play }
enqueue: Integer;
{ low bit set specifies enqueuing, and second bit NOT set specifies that
the media library should use its default behavior as the user configured it (if
enqueue is the default, the low bit will be flipped by the library) }
end;
TmlSendToWinampStruct = mlSendToWinampStruct;
PmlAddToSendToStruct = ^mlAddToSendToStruct;
mlAddToSendToStruct =
record
desc: PChar;
{ str }
context: Integer;
{ context passed by ML_MSG_ONSENDTOBUILD }
user32: Integer;
{ use some unique ptr in memory, you will get it back in ML_MSG_ONSENDTOSELECT... }
end;
TmlAddToSendToStruct = mlAddToSendToStruct;
const
ML_IPC_ADDTOSENDTO = $0400;
ML_IPC_HOOKTITLE = $0440;
{ this is like winamp's IPC_HOOK_TITLES... :) param1 is waHookTitleStruct }
ML_IPC_HOOKEXTINFO = $441;
{ called on IPC_GET_EXTENDED_FILE_INFO_HOOKABLE, param1 is extendedFileInfoStruct }
ML_HANDLEDRAG_FLAG_NOCURSOR = 1;
ML_HANDLEDRAG_FLAG_NAME = 2;
type
PmlDropItemStruct = ^mlDropItemStruct;
mlDropItemStruct =
record
_type: Integer;
{ ML_TYPE_ITEMRECORDLIST, etc }
data: Pointer;
{ NULL if just querying }
result: Integer;
{ filled in by client: -1 if dont allow, 0 if dont know, 1 if allow. }
p: TPoint;
{ cursor pos in screen coordinates }
flags: Integer;
name: PChar;
{ only valid if ML_HANDLEDRAG_FLAG_NAME }
end;
TmlDropItemStruct = mlDropItemStruct;
const
ML_IPC_SKIN_LISTVIEW = $0500;
{ pass the hwnd of your listview. returns a handle to use with ML_IPC_UNSKIN_LISTVIEW }
ML_IPC_UNSKIN_LISTVIEW = $0501;
{ pass the handle you got from ML_IPC_SKIN_LISTVIEW }
ML_IPC_LISTVIEW_UPDATE = $0502;
{ pass the handle you got from ML_IPC_SKIN_LISTVIEW }
ML_IPC_LISTVIEW_DISABLEHSCROLL = $0503;
{ pass the handle you got from ML_IPC_SKIN_LISTVIEW }
ML_IPC_SKIN_COMBOBOX = $0508;
{ pass the hwnd of your combobox to skin, returns a ahndle to use with ML_IPC_UNSKIN_COMBOBOX }
ML_IPC_UNSKIN_COMBOBOX = $0509;
{ pass the handle from ML_IPC_SKIN_COMBOBOX }
ML_IPC_SKIN_WADLG_GETFUNC = $0600;
// 1: return int (*WADlg_getColor)(int idx); // pass this an index, returns a RGB value (passing 0 or > 3 returns NULL)
// 2: return int (*WADlg_handleDialogMsgs)(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 3: return void (*WADlg_DrawChildWindowBorders)(HWND hwndDlg, int *tab, int tabsize); // each entry in tab would be the id | DCW_*
// 32: return void (*childresize_init)(HWND hwndDlg, ChildWndResizeItem *list, int num);
// 33: return void (*childresize_resize)(HWND hwndDlg, ChildWndResizeItem *list, int num);
// 66: return (HFONT) font to use for dialog elements, if desired (0 otherwise)
type
TFNWADlg_getColor =
function(idx: Integer): Integer;
cdecl;
TFNWADlg_handleDialogMsgs =
function(hwndDlg: HWND; uMsg: Cardinal; wParam: WPARAM; lParam: LPARAM): Integer;
cdecl;
TDCWArray =
array of Integer;
TFNWADlg_DrawChildWindowBorders =
procedure(hwndDlg: HWND; tab: TDCWArray; tabsize: Integer);
cdecl;
type
{ Only for the Pascal implementation: }
Textended_info_array =
array of PChar;
{ itemRecord type for use with ML_TYPE_ITEMRECORDLIST, as well as many other functions }
PitemRecord = ^itemRecord;
itemRecord =
record
filename: PChar;
title: PChar;
album: PChar;
artist: PChar;
comment: PChar;
genre: PChar;
year: Integer;
track: Integer;
length: Integer;
extended_info: Textended_info_array;
{C equivalent: "char **extended_info;" }
{ currently defined extended columns (while they are stored internally as integers
they are passed using extended_info as strings):
use getRecordExtendedItem and setRecordExtendedItem to get/set.
for your own internal use, you can set other things, but the following values
are what we use at the moment. Note that setting other things will be ignored
by ML_IPC_DB*.
"RATING" file rating. can be 1-5, or 0 or empty for undefined
"PLAYCOUNT" number of file plays.
"LASTPLAY" last time played, in standard time_t format
"LASTUPD" last time updated in library, in standard time_t format
"FILETIME" last known file time of file, in standard time_t format
"FILESIZE" last known file size, in kilobytes.
"BITRATE" file bitrate, in kbps }
end;
TitemRecord = itemRecord;
TitemRecord_array =
array of TitemRecord;
PitemRecordList = ^itemRecordList;
itemRecordList =
record
Items: TitemRecord_array;
{ C equivalent: "itemRecord *Items;" }
Size: Integer;
Alloc: Integer;
end;
TitemRecordList = itemRecordList;
{
all return 1 on success, -1 on error. or 0 if not supported, maybe?
pass these a mlQueryStruct
results should be zeroed out before running a query, but if you wish you can run multiple queries and
have it concatenate the results. tho it would be more efficient to just make one query that contains both,
as running multiple queries might have duplicates etc.
in general, though, if you need to treat "results" as if they are native, you should use
copyRecordList to save a copy, then free the results using ML_IPC_DB_FREEQUERYRESULTS.
if you need to keep an exact copy that you will only read (and will not modify), then you can
use the one in the mlQueryStruct.
}
const
ML_IPC_DB_RUNQUERY = $0700;
ML_IPC_DB_RUNQUERY_SEARCH = $0701;
{ "query" should be interpreted as keyword search instead of query string }
ML_IPC_DB_RUNQUERY_FILENAME = $0702;
{ searches for one exact filename match of "query" }
ML_IPC_DB_RUNQUERY_INDEX = $703;
{ retrieves item #(int)query }
ML_IPC_DB_FREEQUERYRESULTS = $0705;
{ frees memory allocated by ML_IPC_RUNQUERY (empties results) }
type
PmlQueryStruct = ^mlQueryStruct;
mlQueryStruct =
record
query: PChar;
max_results: Integer;
{ can be 0 for unlimited }
results: TitemRecordList;
{ C equivalent: "itemRecordList results;" }
end;
TmlQueryStruct = mlQueryStruct;
{ pass these an (itemRecord *) to add/update.
note that any NULL fields in the itemRecord won't be updated,
and year, track, or length of -1 prevents updating as well. }
const
ML_IPC_DB_UPDATEITEM = $0706;
{ returns -2 if file not found in db }
ML_IPC_DB_ADDORUPDATEITEM = $0707;
ML_IPC_DB_REMOVEITEM = $0708;
{ pass a char * to the filename to remove. returns -2 if file not found in db. }
ML_IPC_DB_SYNCDB = $0709;
{ sync db if dirty flags are set. good to do after a batch of updates. }
{ these return 0 if unsupported, -1 if failed, 1 if succeeded }
{ pass a winampMediaLibraryPlugin *. Will not call plugin's init() func.
YOU MUST set winampMediaLibraryPlugin->hDllInstance to NULL, and version to MLHDR_VER }
ML_IPC_ADD_PLUGIN = $0750;
ML_IPC_REMOVE_PLUGIN = $0751;
{ winampMediaLibraryPlugin * of plugin to remove. Will not call plugin's quit() func }
{ this gets sent to any child windows of the library windows, and then (if not
handled) the library window itself }
WM_ML_CHILDIPC = WM_APP+$800;
{ avoids conflicts with any windows controls }
ML_CHILDIPC_DROPITEM = $100;
{ lParam = 100, wParam = &mlDropItemStruct }
// current item ratings
ML_IPC_SETRATING = $0900;
{ lParam = 0 to 5, rates current track -- inserts it in the db if it's not in it yet }
ML_IPC_GETRATING = $0901;
{ return the current track's rating or 0 if not in db/no rating }
type
{ playlist entry rating }
Ppl_set_rating = ^pl_set_rating;
pl_set_rating =
record
plentry: Integer;
rating: Integer;
end;
Tpl_set_rating = pl_set_rating;
const
ML_IPC_PL_SETRATING = $0902;
{ lParam = pointer to pl_set_rating struct }
ML_IPC_PL_GETRATING = $0903;
{ lParam = playlist entry, returns the rating or 0 if not in db/no rating }
type
Pml_editquery = ^ml_editquery;
ml_editquery =
record
dialog_parent: HWND;
{ Use this window as a parent for the query editor dialog }
{const} query: PChar;
{ The query to edit, or "" / null for new query }
end;
Tml_editquery = ml_editquery;
const
ML_IPC_EDITQUERY = $904;
{ lParam = pointer to ml_editquery struct, returns 0 if edition was canceled and 1 on success }
{ After returning, and if ok was clicked, the struct contains a pointer to the edited query. this pointer is static : }
{ - do *not* free it }
{ - if you need to keep it around, strdup it, as it may be changed later by other plugins calling ML_IPC_EDITQUERY. }
type
Pml_editview = ^ml_editview;
ml_editview =
record
dialog_parent: HWND;
{ Use this window as a parent for the view editor dialog }
{const} query: PChar;
{ The query to edit, or "" / null for new views }
{const} name: PChar;
{ Name of the view (ignored for new views) }
mode: Integer;
{ View mode (0=simple view, 1=artist/album view, -1=hide mode radio boxes) }
end;
Tml_editview = ml_editview;
const
ML_IPC_EDITVIEW = $905;
{ lParam = pointer to ml_editview struct, returns 0 if edition was canceled and 1 on success }
{ After returning, and if ok was clicked, the struct contains the edited values. String pointers are static: }
{ - do *not* free them }
{ - if you need to keep them around, strdup them, as they may be changed later by other plugins calling ML_IPC_EDITQUERY. }
{ utility functions in implementation part of this unit }
{$IFDEF HAS_TRANSLATED_TO_PASCAL}
procedure freeRecordList(obj: PitemRecordList);
procedure emptyRecordList(obj: PitemRecordList);
{ does not free Items }
procedure freeRecord(p: PitemRecord);
{ if out has items in it copyRecordList will append "in" to "out". }
procedure copyRecordList(_out: PitemRecordList; _in: PitemRecordList);
{ copies a record }
procedure copyRecord(_out: PitemRecord; _in: PitemRecord);
procedure allocRecordList(obj: PitemRecordList; newsize, granularity: Integer = 1024);
{$ENDIF}
{ Original C/C++ code
void allocRecordList(itemRecordList *obj, int newsize, int granularity
#ifdef __cplusplus
=1024
#endif
);
}
function getRecordExtendedItem(item: PitemRecord;
name: PChar): PChar;
procedure setRecordExtendedItem(item: PitemRecord;
name, value: PChar);
implementation
{$IFDEF HAS_TRANSLATED_TO_PASCAL}
procedure freeRecord(p: PitemRecord);
begin
free(p->title);
free(p->artist);
free(p->comment);
free(p->album);
free(p->genre);
free(p->filename);
if (p->extended_info)
{
int x=0;
for (x = 0; p->extended_info[x]; x ++)
free(p->extended_info[x]);
free(p->extended_info);
}
memset(p,0,sizeof(itemRecord));
end;
procedure freeRecordList(obj: PitemRecordList);
begin
emptyRecordList(obj);
free(obj->Items);
obj->Items=0;
obj->Alloc=obj->Size=0;
end;
procedure emptyRecordList(obj: PitemRecordList);
begin
itemRecord *p=obj->Items;
while (obj->Size-->0)
{
freeRecord(p);
p++;
}
obj->Size=0;
end;
procedure allocRecordList(obj: PitemRecordList; newsize, granularity: Integer = 1024);
begin
if (newsize < obj->Alloc || newsize < obj->Size) return;
obj->Alloc=newsize+granularity;
obj->Items=(itemRecord*)realloc(obj->Items,sizeof(itemRecord)*obj->Alloc);
if (!obj->Items) obj->Alloc=0;
end;
procedure copyRecord(_out: PitemRecord; _in: PitemRecord);
begin
int y;
#define COPYSTR(FOO)
out->FOO =
in->FOO ? strdup(
in->FOO) : 0;
COPYSTR(filename)
COPYSTR(title)
COPYSTR(album)
COPYSTR(artist)
COPYSTR(comment)
COPYSTR(genre)
out->year=in->year;
out->track=in->track;
out->length=in->length;
#undef COPYSTR
out->extended_info=0;
if (
in->extended_info)
for (y = 0;
in->extended_info[y]; y ++)
{
char *p=in->extended_info[y];
if (*p) setRecordExtendedItem(out,p,p+strlen(p)+1);
}
end;
procedure copyRecordList(_out: PitemRecordList; _in: PitemRecordList);
begin
int x;
allocRecordList(
out,
out->Size+
in->Size,0);
if (!out->Items) return;
for (x = 0; x <
in->Size; x ++)
{
copyRecord(&out->Items[out->Size++],&in->Items[x]);
}
end;
{$ENDIF}
function getRecordExtendedItem(item: PitemRecord;
name: PChar): PChar;
var
x: Integer;
begin
x := 0;
result :=
nil;
if item^.extended_info <>
nil then begin
while item^.extended_info[x] <>
nil do begin
if lstrcmpi(item^.extended_info[x],
name) = 0
then begin
result := item^.extended_info[x]+lstrlen(
name)+1;
Exit;
end;
Inc(x);
end;
end;
end;
procedure setRecordExtendedItem(item: PitemRecord;
name, value: PChar);
var
x: Integer;
begin
x := 0;
if item^.extended_info <>
nil then begin
while item^.extended_info[x] <>
nil do begin
if lstrcmpi(item^.extended_info[x],
name) = 0
then begin
if lstrlen(value)>lstrlen(item^.extended_info[x])+lstrlen(
name)+1
then
begin
HeapFree(GetProcessHeap, 0, item^.extended_info[x]);
item^.extended_info[x] := PChar( HeapAlloc(GetProcessHeap, 0, lstrlen(
name)+lstrlen(value)+2) );
end;
lstrcpy(item^.extended_info[x],
name);
lstrcpy(item^.extended_info[x]+lstrlen(
name)+1, value);
Exit;
end;
Inc(x);
end;
end;
{ x=number of valid items. }
HeapRealloc(GetProcessHeap(), 0, item^.extended_info, sizeof(PChar) * (x+2));
if item^.extended_info <>
nil then begin
item^.extended_info[x] := PChar( HeapAlloc(GetProcessHeap, 0, lstrlen(
name)+lstrlen(value)+2) );
lstrcpy(item^.extended_info[x],
name);
lstrcpy(item^.extended_info[x]+lstrlen(
name)+1, value);
item^.extended_info[x+1] := #0;
end;
end;
end.