/* 'File Association Manager'-Plugin for Miranda IM Copyright (C) 2005-2007 H. Herkenrath This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program (AssocMgr-License.txt); if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "common.h" /* Options */ extern HINSTANCE hInst; static HANDLE hHookOptInit; /* Services */ static HANDLE hServiceAddFile,hServiceRemoveFile,hServiceAddUrl,hServiceRemoveUrl; /************************* Assoc List *****************************/ typedef struct { char *pszClassName; /* class name as used in registry and db */ TCHAR *pszDescription; HINSTANCE hInstance; /* allowed to be NULL for miranda32.exe */ WORD nIconResID; char *pszService; WORD flags; /* set of FTDF_* and UTDF_* flags */ char *pszFileExt; /* file type: NULL for url type*/ char *pszMimeType; /* file type: allowed to be NULL */ TCHAR *pszVerbDesc; /* file type: allowed to be NULL */ } ASSOCDATA; static ASSOCDATA *pAssocList; /* protected by csAssocList */ static int nAssocListCount; /* protected by csAssocList */ static CRITICAL_SECTION csAssocList; /************************* Assoc Enabled **************************/ static BOOL IsAssocEnabled(const ASSOCDATA *assoc) { char szSetting[MAXMODULELABELLENGTH]; mir_snprintf(szSetting,sizeof(szSetting),"enabled_%s",assoc->pszClassName); return DBGetContactSettingByte(NULL,"AssocMgr",szSetting,(BYTE)!(assoc->flags&FTDF_DEFAULTDISABLED))!=0; } static void SetAssocEnabled(const ASSOCDATA *assoc,BOOL fEnabled) { char szSetting[MAXMODULELABELLENGTH]; TCHAR szDLL[MAX_PATH],szBuf[MAX_PATH]; mir_snprintf(szSetting,sizeof(szSetting),"enabled_%s",assoc->pszClassName); DBWriteContactSettingByte(NULL,"AssocMgr",szSetting,(BYTE)fEnabled); /* dll name for uninstall */ if(assoc->hInstance!=NULL && assoc->hInstance!=hInst && assoc->hInstance!=GetModuleHandle(NULL)) if(GetModuleFileName(assoc->hInstance,szBuf,SIZEOF(szBuf))) if(CallService(MS_UTILS_PATHTORELATIVET,(WPARAM)szBuf,(LPARAM)szDLL)) { mir_snprintf(szSetting,sizeof(szSetting),"module_%s",assoc->pszClassName); DBWriteContactSettingTString(NULL,"AssocMgr",szSetting,szDLL); } } static void DeleteAssocEnabledSetting(const ASSOCDATA *assoc) { char szSetting[MAXMODULELABELLENGTH]; mir_snprintf(szSetting,sizeof(szSetting),"enabled_%s",assoc->pszClassName); DBDeleteContactSetting(NULL,"AssocMgr",szSetting); /* dll name for uninstall */ mir_snprintf(szSetting,sizeof(szSetting),"module_%s",assoc->pszClassName); DBDeleteContactSetting(NULL,"AssocMgr",szSetting); } void CleanupAssocEnabledSettings(void) { int nSettingsCount; char **ppszSettings,*pszSuffix; DBVARIANT dbv; int i; HANDLE hFile; TCHAR szDLL[MAX_PATH]; char szSetting[MAXMODULELABELLENGTH]; /* delete old enabled_* settings if associated plugin no longer present */ if(EnumDbPrefixSettings("AssocMgr","enabled_",&ppszSettings,&nSettingsCount)) { EnterCriticalSection(&csAssocList); for(i=0;ipszClassName=mir_strdup(assoc->pszClassName); assoc2->pszDescription=mir_tstrdup(assoc->pszDescription); assoc2->hInstance=assoc->hInstance; assoc2->nIconResID=assoc->nIconResID; assoc2->pszService=mir_strdup(assoc->pszService); assoc2->flags=assoc->flags; assoc2->pszFileExt=mir_strdup(assoc->pszFileExt); assoc2->pszMimeType=mir_strdup(assoc->pszMimeType); assoc2->pszVerbDesc=mir_tstrdup(assoc->pszVerbDesc); if(assoc2->pszClassName==NULL || assoc2->pszDescription==NULL || (assoc2->pszFileExt==NULL && assoc->pszFileExt!=NULL)) { mir_free(assoc2->pszClassName); /* does NULL check */ mir_free(assoc2->pszDescription); /* does NULL check */ mir_free(assoc2->pszService); /* does NULL check */ mir_free(assoc2->pszFileExt); /* does NULL check */ mir_free(assoc2->pszMimeType); /* does NULL check */ mir_free(assoc2->pszVerbDesc); /* does NULL check */ mir_free(assoc2); return NULL; } return assoc2; } // this function assumes it has got the csAssocList mutex // this function assumes CoInitialize() has been called before static int ReplaceImageListAssocIcon(HIMAGELIST himl,const ASSOCDATA *assoc,int iPrevIndex) { HICON hIcon=NULL; int index; if(himl==NULL) return -1; /* load icon */ hIcon=LoadRegClassSmallIcon(assoc->pszClassName); if(hIcon==NULL) { SHFILEINFOA sfi; if(SHGetFileInfoA((assoc->pszFileExt!=NULL)?assoc->pszFileExt:"",FILE_ATTRIBUTE_NORMAL,&sfi,sizeof(sfi),SHGFI_ICON|SHGFI_SMALLICON|SHGFI_USEFILEATTRIBUTES)) hIcon=sfi.hIcon; /* WinXP: this icon is not updated until the process exits */ } /* add icon */ if(hIcon==NULL) return -1; index=ImageList_ReplaceIcon(himl,iPrevIndex,hIcon); DestroyIcon(hIcon); return index; } // the return value does not need to be freed // this function assumes it has got the csAssocList mutex static TCHAR* GetAssocTypeDesc(const ASSOCDATA *assoc) { static TCHAR szDesc[32]; if(assoc->pszFileExt==NULL) mir_sntprintf(szDesc,SIZEOF(szDesc),TranslateT("%hs:"),assoc->pszClassName); else mir_sntprintf(szDesc,SIZEOF(szDesc),TranslateT("%hs files"),assoc->pszFileExt); return szDesc; } // this function assumes it has got the csAssocList mutex static BOOL IsAssocRegistered(const ASSOCDATA *assoc) { BOOL fSuccess=FALSE,fIsUrl,fUseMainCmdLine; TCHAR *pszRunCmd; fIsUrl=(assoc->pszFileExt==NULL); fUseMainCmdLine=(assoc->pszService==NULL); /* class */ pszRunCmd=MakeRunCommand(fUseMainCmdLine,!fUseMainCmdLine); if(pszRunCmd!=NULL) fSuccess=IsRegClass(assoc->pszClassName,pszRunCmd); mir_free(pszRunCmd); /* does NULL check */ /* file ext */ if(!fIsUrl) fSuccess=IsRegFileExt(assoc->pszFileExt,assoc->pszClassName); return fSuccess; } // this function assumes it has got the csAssocList mutex // call GetLastError() on error to get more error details static BOOL EnsureAssocRegistered(const ASSOCDATA *assoc) { BOOL fSuccess=FALSE,fIsUrl,fUseMainCmdLine; TCHAR *pszIconLoc,*pszRunCmd,*pszDdeCmd,*pszAppFileName; fIsUrl=(assoc->pszFileExt==NULL); fUseMainCmdLine=(assoc->pszService==NULL); pszRunCmd=MakeRunCommand(fUseMainCmdLine,!fUseMainCmdLine); if(pszRunCmd!=NULL) { fSuccess=TRUE; /* tentatively */ /* do not overwrite user customized settings */ if(!IsRegClass(assoc->pszClassName,pszRunCmd)) { /* class icon */ if(!assoc->nIconResID && fIsUrl) pszIconLoc=MakeIconLocation(NULL,0); /* miranda logo */ else if(!assoc->nIconResID) pszIconLoc=MakeIconLocation(hInst,IDI_MIRANDAFILE); /* generic file */ else pszIconLoc=MakeIconLocation(assoc->hInstance,assoc->nIconResID); /* register class */ if(fUseMainCmdLine) pszDdeCmd=NULL; else pszDdeCmd=fIsUrl?DDEURLCMD:DDEFILECMD; fSuccess=AddRegClass(assoc->pszClassName,assoc->pszDescription,pszIconLoc,_T(MIRANDANAME),pszRunCmd,pszDdeCmd,DDEAPP,DDETOPIC,assoc->pszVerbDesc,assoc->flags&FTDF_BROWSERAUTOOPEN,fIsUrl,assoc->flags&FTDF_ISSHORTCUT); mir_free(pszIconLoc); /* does NULL check */ /* file type */ if(fSuccess && !fIsUrl) { /* register mime type */ if(assoc->pszMimeType!=NULL) if(AddRegMimeType(assoc->pszMimeType,assoc->pszFileExt,assoc->pszDescription)) RememberMimeTypeAdded(assoc->pszMimeType,assoc->pszFileExt,TRUE); /* register file ext */ fSuccess=AddRegFileExt(assoc->pszFileExt,assoc->pszClassName,assoc->pszMimeType,assoc->flags&FTDF_ISTEXT); /* register open-with */ pszAppFileName=MakeAppFileName(fUseMainCmdLine); if(pszAppFileName!=NULL) AddRegOpenWithExtEntry(pszAppFileName,assoc->pszFileExt,assoc->pszDescription); mir_free(pszAppFileName); /* does NULL check */ } } mir_free(pszRunCmd); } else SetLastError(ERROR_OUTOFMEMORY); return fSuccess; } // this function assumes it has got the csAssocList mutex // call GetLastError() on error to get more error details static BOOL UnregisterAssoc(const ASSOCDATA *assoc) { BOOL fIsUrl,fUseMainCmdLine; TCHAR *pszAppFileName,*pszRunCmd; fIsUrl=(assoc->pszFileExt==NULL); fUseMainCmdLine=(assoc->pszService==NULL); /* class might have been registered by another instance */ pszRunCmd=MakeRunCommand(fUseMainCmdLine,!fUseMainCmdLine); if(pszRunCmd!=NULL && !IsRegClass(assoc->pszClassName,pszRunCmd)) { mir_free(pszRunCmd); return TRUE; /* succeed anyway */ } mir_free(pszRunCmd); /* does NULL check */ /* file type */ if(!fIsUrl) { /* file extension */ RemoveRegFileExt(assoc->pszFileExt,assoc->pszClassName); /* mime type */ if(assoc->pszMimeType!=NULL) if(WasMimeTypeAdded(assoc->pszMimeType)) { RemoveRegMimeType(assoc->pszMimeType,assoc->pszFileExt); RememberMimeTypeAdded(assoc->pszMimeType,assoc->pszFileExt,FALSE); } /* open-with entry */ pszAppFileName=MakeAppFileName(fUseMainCmdLine); if(pszAppFileName!=NULL) RemoveRegOpenWithExtEntry(pszAppFileName,assoc->pszFileExt); mir_free(pszAppFileName); /* does NULL check */ } return RemoveRegClass(assoc->pszClassName); } /************************* Assoc List Workers *********************/ /* this structure represents the head of both * FILETYPEDESC and URLTYPEDESC structures. * the head is identical for both structures. */ typedef struct { int cbSize; /* either sizeof(FILETYPEDESC) or sizeof(URLTYPEDESC) */ const void *pszDescription; HINSTANCE hInstance; UINT nIconResID; const char *pszService; DWORD flags; } TYPEDESCHEAD; // ownership of pszClassName, pszFileExt, pszVerbDesc and pszMimeType is transfered // to the storage list on success static BOOL AddNewAssocItem_Worker(char *pszClassName,const TYPEDESCHEAD *tdh,char *pszFileExt,TCHAR *pszVerbDesc,char *pszMimeType) { ASSOCDATA *pAssocListBuf,*assoc; int index; /* is already in list? */ EnterCriticalSection(&csAssocList); index=FindAssocItem(pszClassName); if(index!=-1) return FALSE; /* resize storage array */ pAssocListBuf=(ASSOCDATA*)mir_realloc(pAssocList,(nAssocListCount+1)*sizeof(ASSOCDATA)); if(pAssocListBuf==NULL) { LeaveCriticalSection(&csAssocList); return FALSE; } pAssocList=pAssocListBuf; /* init new item */ assoc=&pAssocList[nAssocListCount]; assoc->pszClassName=pszClassName; /* no dup here */ assoc->pszDescription=s2t(tdh->pszDescription,tdh->flags&FTDF_UNICODE,TRUE); /* does NULL check */ assoc->hInstance=tdh->hInstance; /* hInstance is allowed to be NULL for miranda32.exe */ assoc->nIconResID=(WORD)tdh->nIconResID; /* default icon selected later on */ assoc->pszService=mir_strdup(tdh->pszService); /* does NULL check */ assoc->flags=(WORD)tdh->flags; assoc->pszFileExt=pszFileExt; assoc->pszMimeType=pszMimeType; assoc->pszVerbDesc=pszVerbDesc; /* error check */ if(assoc->pszDescription==NULL || (assoc->pszService==NULL && tdh->pszService!=NULL)) { mir_free(assoc->pszService); /* does NULL check */ mir_free(assoc->pszDescription); /* does NULL check */ LeaveCriticalSection(&csAssocList); return FALSE; } /* add registry keys */ if(IsAssocEnabled(assoc)) EnsureAssocRegistered(assoc); ++nAssocListCount; NotifyAssocChange(FALSE); return TRUE; } // ownership of pszClassName is *not* transferd to storage list static BOOL RemoveAssocItem_Worker(const char *pszClassName) { ASSOCDATA *pAssocListBuf,*assoc; int index; /* find index */ EnterCriticalSection(&csAssocList); index=FindAssocItem(pszClassName); if(index==-1) { LeaveCriticalSection(&csAssocList); return FALSE; } assoc=&pAssocList[index]; /* delete registry keys and db setting */ UnregisterAssoc(assoc); if(assoc->pszMimeType!=NULL) RememberMimeTypeAdded(assoc->pszMimeType,assoc->pszFileExt,FALSE); DeleteAssocEnabledSetting(assoc); /* free memory */ mir_free(assoc->pszClassName); mir_free(assoc->pszDescription); mir_free(assoc->pszService); mir_free(assoc->pszFileExt); /* does NULL check */ mir_free(assoc->pszVerbDesc); /* does NULL check */ mir_free(assoc->pszMimeType); /* does NULL check */ /* resize storage array */ if((index+1)cbSizepszFileExt==NULL || ftd->pszFileExt[0]!='.') return 2; pszFileExt=mir_strdup(ftd->pszFileExt); pszClassName=MakeFileClassName(ftd->pszFileExt); if(pszFileExt!=NULL && pszClassName!=NULL) { pszVerbDesc=s2t(ftd->ptszVerbDesc,ftd->flags&FTDF_UNICODE,TRUE); /* does NULL check */ pszMimeType=mir_strdup(ftd->pszMimeType); /* does NULL check */ if(AddNewAssocItem_Worker(pszClassName,(TYPEDESCHEAD*)ftd,pszFileExt,pszVerbDesc,pszMimeType)) /* no need to free pszClassName, pszFileExt,pszVerbDesc and pszMimeType, * as their ownership got transfered to storage list */ return 0; } mir_free(pszClassName); /* does NULL check */ mir_free(pszFileExt); /* does NULL check */ return 3; } static int ServiceRemoveFileType(WPARAM wParam,LPARAM lParam) { char *pszClassName; UNREFERENCED_PARAMETER(wParam); if((char*)lParam==NULL) return 2; pszClassName=MakeFileClassName((char*)lParam); if(pszClassName!=NULL) if(RemoveAssocItem_Worker(pszClassName)) { mir_free(pszClassName); return 0; } mir_free(pszClassName); /* does NULL check */ return 3; } static int ServiceAddNewUrlType(WPARAM wParam,LPARAM lParam) { const URLTYPEDESC *utd=(URLTYPEDESC*)lParam; char *pszClassName; UNREFERENCED_PARAMETER(wParam); if(utd->cbSizepszService==NULL) return 2; if(utd->pszProtoPrefix==NULL || utd->pszProtoPrefix[lstrlenA(utd->pszProtoPrefix)-1]!=':') return 2; pszClassName=MakeUrlClassName(utd->pszProtoPrefix); if(pszClassName!=NULL) if(AddNewAssocItem_Worker(pszClassName,(TYPEDESCHEAD*)utd,NULL,NULL,NULL)) /* no need to free pszClassName, as its * ownership got transfered to storage list */ return 0; mir_free(pszClassName); /* does NULL check */ return 3; } static int ServiceRemoveUrlType(WPARAM wParam,LPARAM lParam) { char *pszClassName; UNREFERENCED_PARAMETER(wParam); if((char*)lParam==NULL) return 2; pszClassName=MakeUrlClassName((char*)lParam); if(pszClassName!=NULL) if(RemoveAssocItem_Worker(pszClassName)) { mir_free(pszClassName); return 0; } mir_free(pszClassName); /* does NULL check */ return 3; } /************************* Open Handler ***************************/ static BOOL InvokeHandler_Worker(const char *pszClassName,const TCHAR *pszParam,int *res) { void *pvParam; char *pszService; int index; ASSOCDATA *assoc; /* find it in list */ EnterCriticalSection(&csAssocList); index=FindAssocItem(pszClassName); if(index==-1) { LeaveCriticalSection(&csAssocList); return FALSE; } assoc=&pAssocList[index]; /* no service specified? correct registry to use main commandline */ if(assoc->pszService==NULL) { EnsureAssocRegistered(assoc); NotifyAssocChange(FALSE); /* try main command line */ if((int)ShellExecute(NULL,NULL,pszParam,NULL,NULL,SW_SHOWNORMAL)>=32) *res=0; /* success */ return TRUE; } /* get params */ pszService=mir_strdup(assoc->pszService); pvParam=t2s(pszParam,assoc->flags&FTDF_UNICODE,FALSE); LeaveCriticalSection(&csAssocList); /* call service */ if(pszService!=NULL && pvParam!=NULL) *res=CallService(pszService,0,(LPARAM)pvParam); mir_free(pszService); /* does NULL check */ mir_free(pvParam); /* does NULL check */ return TRUE; } int InvokeFileHandler(const TCHAR *pszFileName) { char *pszClassName,*pszFileExt; TCHAR *p; int res=CALLSERVICE_NOTFOUND; /* find extension */ p=_tcsrchr(pszFileName,_T('.')); if(p!=NULL) { pszFileExt=t2a(p); if(pszFileExt!=NULL) { /* class name */ pszClassName=MakeFileClassName(pszFileExt); if(pszClassName!=NULL) if(!InvokeHandler_Worker(pszClassName,pszFileName,&res)) { /* correct registry on error (no longer in list) */ RemoveRegFileExt(pszFileExt,pszClassName); RemoveRegClass(pszClassName); } mir_free(pszClassName); /* does NULL check */ mir_free(pszFileExt); } } return res; } int InvokeUrlHandler(const TCHAR *pszUrl) { char *pszClassName,*pszProtoPrefix,*p; int res=CALLSERVICE_NOTFOUND; /* find prefix */ pszProtoPrefix=t2a(pszUrl); if(pszProtoPrefix!=NULL) { p=strchr(pszProtoPrefix,_T(':')); if(p!=NULL) { *(++p)=0; /* remove trailing : */ /* class name */ pszClassName=MakeUrlClassName(pszProtoPrefix); if(pszClassName!=NULL) if(!InvokeHandler_Worker(pszClassName,pszUrl,&res)) /* correct registry on error (no longer in list) */ RemoveRegClass(pszClassName); mir_free(pszClassName); /* does NULL check */ } mir_free(pszProtoPrefix); } return res; } /************************* Options ********************************/ static int CALLBACK ListViewSortDesc(LPARAM lParam1,LPARAM lParam2,LPARAM lParamSort) { int cmp; if(((ASSOCDATA*)lParam1)->pszFileExt!=NULL && ((ASSOCDATA*)lParam2)->pszFileExt!=NULL) cmp=CompareStringA((LCID)lParamSort,0,((ASSOCDATA*)lParam1)->pszFileExt,-1,((ASSOCDATA*)lParam2)->pszFileExt,-1); else if(((ASSOCDATA*)lParam1)->pszFileExt==((ASSOCDATA*)lParam2)->pszFileExt) /* both NULL */ cmp=CompareStringA((LCID)lParamSort,0,((ASSOCDATA*)lParam1)->pszClassName,-1,((ASSOCDATA*)lParam2)->pszClassName,-1); else /* different types, incomparable */ cmp=(((ASSOCDATA*)lParam1)->pszFileExt==NULL)?CSTR_LESS_THAN:CSTR_GREATER_THAN; if(cmp==CSTR_EQUAL) cmp=CompareString((LCID)lParamSort,0,((ASSOCDATA*)lParam1)->pszDescription,-1,((ASSOCDATA*)lParam2)->pszDescription,-1); if(cmp!=0) cmp-=2; /* maintain CRT conventions */ return cmp; } #define M_REFRESH_ICONS (WM_APP+1) static int CALLBACK AssocListOptDlgProc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam) { switch(msg) { case WM_INITDIALOG: { HWND hwndList; HIMAGELIST himl; LVITEM lvi; ASSOCDATA *assoc; int i; TranslateDialogDefault(hwndDlg); CoInitialize(NULL); hwndList=GetDlgItem(hwndDlg,IDC_ASSOCLIST); #if defined(_UNICODE) ListView_SetUnicodeFormat(hwndList,TRUE); #endif SendDlgItemMessage(hwndDlg,IDC_HEADERTEXT,WM_SETFONT,SendMessage(GetParent(hwndDlg),PSM_GETBOLDFONT,0,0),0); /* checkboxes won't show up on Win95 without IE3+ or 4.70 (plugin opts uses the same) */ ListView_SetExtendedListViewStyle(hwndList,LVS_EX_CHECKBOXES|LVS_EX_FULLROWSELECT|LVS_EX_LABELTIP); /* columns */ { LVCOLUMN lvc; lvc.mask=LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM; lvc.pszText=TranslateT("Type"); lvc.cx=170; ListView_InsertColumn(hwndList,lvc.iSubItem=0,&lvc); lvc.pszText=TranslateT("Description"); ListView_InsertColumn(hwndList,lvc.iSubItem=1,&lvc); } /* create image storage */ EnterCriticalSection(&csAssocList); { HDC hdc; hdc=GetDC(hwndList); if(hdc!=NULL) { /* BITSPIXEL is compatible with ILC_COLOR flags */ himl=ImageList_Create(GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON),GetDeviceCaps(hdc,BITSPIXEL)|ILC_MASK,nAssocListCount,0); ReleaseDC(hwndList,hdc); } else himl=NULL; } ListView_SetImageList(hwndList,himl,LVSIL_SMALL); /* autodestroyed */ /* enum assoc list */ lvi.iSubItem=0; lvi.mask=LVIF_TEXT|LVIF_PARAM|LVIF_IMAGE; for(i=0;ipszDescription); ListView_SetCheckState(hwndList,lvi.iItem,IsAssocEnabled(assoc) && IsAssocRegistered(assoc)); } } /* sort items (before moving to groups) */ ListView_SortItems(hwndList,ListViewSortDesc,(LPARAM)CallService(MS_LANGPACK_GETLOCALE,0,0)); /* groups */ if(ListView_EnableGroupView(hwndList,TRUE)==1) { /* returns 0 on pre WinXP or if commctls6 are disabled */ LVGROUP lvg; int iItem; /* dummy item for group */ lvi.iItem=ListView_GetItemCount(hwndList)-1; lvi.iSubItem=0; lvi.mask=LVIF_PARAM|LVIF_IMAGE; lvi.iImage=-1; lvi.lParam=0; /* insert groups */ lvg.cbSize=sizeof(lvg); lvg.mask=LVGF_HEADER|LVGF_GROUPID; lvg.iGroupId=2; lvg.pszHeader=(WCHAR*)TranslateW(L"URLs on Websites"); lvi.iItem=ListView_InsertItem(hwndList,&lvi); if(lvi.iItem!=-1) { ListView_InsertGroup(hwndList,lvi.iItem,&lvg); lvg.iGroupId=1; lvg.pszHeader=(WCHAR*)TranslateW(L"File Types"); iItem=lvi.iItem=ListView_InsertItem(hwndList,&lvi); if(lvi.iItem!=-1) ListView_InsertGroup(hwndList,lvi.iItem,&lvg); else ListView_DeleteItem(hwndList,iItem); } /* move to group */ lvi.iSubItem=0; lvi.mask=LVIF_PARAM|LVIF_GROUPID; for(lvi.iItem=0;ListView_GetItem(hwndList,&lvi);++lvi.iItem) { assoc=(ASSOCDATA*)lvi.lParam; if(assoc==NULL) continue; /* groups */ lvi.iGroupId=(assoc->pszFileExt==NULL)+1; ListView_SetItem(hwndList,&lvi); } } LeaveCriticalSection(&csAssocList); lvi.iItem=ListView_GetTopIndex(hwndList); ListView_SetItemState(hwndList,lvi.iItem,LVIS_SELECTED|LVIS_FOCUSED,LVIS_SELECTED|LVIS_FOCUSED); ListView_SetColumnWidth(hwndList,1,LVSCW_AUTOSIZE_USEHEADER); /* size to fit window */ /* only while running */ CheckDlgButton(hwndDlg,IDC_ONLYWHILERUNNING,(BOOL)DBGetContactSettingByte(NULL,"AssocMgr","OnlyWhileRunning",SETTING_ONLYWHILERUNNING_DEFAULT)); /* autostart */ { TCHAR *pszRunCmd; pszRunCmd=MakeRunCommand(TRUE,TRUE); if(pszRunCmd!=NULL) { CheckDlgButton(hwndDlg,IDC_AUTOSTART,IsRegRunEntry(_T("MirandaIM"),pszRunCmd)); mir_free(pszRunCmd); } } return TRUE; } case WM_SETTINGCHANGE: case M_REFRESH_ICONS: { LVITEM lvi; HIMAGELIST himl; ASSOCDATA *assoc; HWND hwndList; hwndList=GetDlgItem(hwndDlg,IDC_ASSOCLIST); himl=ListView_GetImageList(hwndList,LVSIL_SMALL); /* enum items */ lvi.iSubItem=0; lvi.mask=LVIF_PARAM|LVIF_IMAGE; for(lvi.iItem=0;ListView_GetItem(hwndList,&lvi);++lvi.iItem) { assoc=(ASSOCDATA*)lvi.lParam; if(assoc==NULL) continue; /* groups */ lvi.iImage=ReplaceImageListAssocIcon(himl,assoc,lvi.iImage); ListView_SetItem(hwndList,&lvi); } if(lvi.iItem) { /* ListView_Update() blinks */ ListView_RedrawItems(hwndList,0,lvi.iItem-1); UpdateWindow(hwndList); } return TRUE; } case WM_CTLCOLORSTATIC: /* use same text color for header as for group boxes (WinXP+) */ if(GetDlgCtrlID((HWND)lParam)==IDC_HEADERTEXT) { HMODULE hUxThemeDLL; HBRUSH hBrush; hUxThemeDLL=LoadLibraryA("UXTHEME"); /* all ascii, already loaded */ lParam=(LPARAM)GetDlgItem(hwndDlg,IDC_MISCLABEL); hBrush=(HBRUSH)SendMessage(hwndDlg,msg,wParam,lParam); if(hUxThemeDLL!=NULL) { HTHEME (WINAPI *pfnGetWindowTheme)(HWND); HRESULT (WINAPI *pfnGetThemeColor)(HTHEME,int,int,int,COLORREF*); COLORREF clr; *(PROC*)&pfnGetWindowTheme=GetProcAddress(hUxThemeDLL,"GetWindowTheme"); *(PROC*)&pfnGetThemeColor=GetProcAddress(hUxThemeDLL,"GetThemeColor"); if(pfnGetWindowTheme!=NULL && pfnGetThemeColor!=NULL) { HTHEME hTheme; hTheme=pfnGetWindowTheme((HWND)lParam); if(hTheme!=NULL) if(!pfnGetThemeColor(hTheme,BP_GROUPBOX,GBS_NORMAL,TMT_TEXTCOLOR,&clr)) { SetBkMode((HDC)wParam,TRANSPARENT); SetTextColor((HDC)wParam,clr); } } FreeLibrary(hUxThemeDLL); } return (BOOL)hBrush; } break; case WM_NCDESTROY: CoUninitialize(); return TRUE; case WM_COMMAND: /* enable apply */ PostMessage(GetParent(hwndDlg),PSM_CHANGED,0,0); break; #if defined(_UNICODE) case WM_NOTIFYFORMAT: SetWindowLong(hwndDlg,DWL_MSGRESULT,NFR_UNICODE); return TRUE; #endif case WM_NOTIFY: { NMHDR *nmhdr=(NMHDR*)lParam; switch(nmhdr->idFrom) { case IDC_ASSOCLIST: switch(nmhdr->code) { case LVN_DELETEITEM: /* also called on WM_DESTROY */ { LVITEM lvi; lvi.mask=LVIF_PARAM; lvi.iSubItem=0; lvi.iItem=((NMLISTVIEW*)lParam)->iItem; /* free memory */ if(ListView_GetItem(nmhdr->hwndFrom,&lvi)) mir_free((ASSOCDATA*)lvi.lParam); /* does NULL check */ return TRUE; } case LVN_ITEMCHANGED: /* enable apply (not while loading) */ if(IsWindowVisible(nmhdr->hwndFrom)) PostMessage(GetParent(hwndDlg),PSM_CHANGED,0,0); return TRUE; case LVN_KEYDOWN: /* workaround for WinXP (ListView with groups): * eat keyboard navigation that goes beyond the first item in list * as it would scroll out of scope in this case * bug should not be present using WinVista and higher */ switch(((NMLVKEYDOWN*)lParam)->wVKey) { case VK_UP: { LVITEM lvi; lvi.iSubItem=0; lvi.mask=LVIF_PARAM; lvi.iItem=ListView_GetNextItem(nmhdr->hwndFrom,-1,LVNI_FOCUSED); lvi.iItem=ListView_GetNextItem(nmhdr->hwndFrom,lvi.iItem,LVNI_ABOVE); if(lvi.iItem!=-1) if(ListView_GetItem(nmhdr->hwndFrom,&lvi)) if((ASSOCDATA*)lvi.lParam==NULL) /* groups */ lvi.iItem=-1; if(lvi.iItem==-1) { SetWindowLong(hwndDlg,DWL_MSGRESULT,TRUE); /* eat it */ return TRUE; } break; } case VK_PRIOR: { LVITEM lvi; lvi.iSubItem=0; lvi.mask=LVIF_PARAM; lvi.iItem=ListView_GetNextItem(nmhdr->hwndFrom,-1,LVNI_FOCUSED); lvi.iItem-=ListView_GetCountPerPage(nmhdr->hwndFrom); if(lvi.iItem>=0) if(ListView_GetItem(nmhdr->hwndFrom,&lvi)) if((ASSOCDATA*)lvi.lParam==NULL) /* groups */ lvi.iItem=-1; if(lvi.iItem<0) { ListView_SetItemState(nmhdr->hwndFrom,0,LVIS_SELECTED|LVIS_FOCUSED,LVIS_SELECTED|LVIS_FOCUSED); SetWindowLong(hwndDlg,DWL_MSGRESULT,TRUE); /* eat it */ return TRUE; } break; } } break; } break; case 0: switch(nmhdr->code) { case PSN_APPLY: { HWND hwndList; LVITEM lvi; BOOL fEnabled,fRegFailed=FALSE; ASSOCDATA *assoc; /* only while running */ DBWriteContactSettingByte(NULL,"AssocMgr","OnlyWhileRunning",(BYTE)(IsDlgButtonChecked(hwndDlg,IDC_ONLYWHILERUNNING)!=0)); /* save enabled assoc items */ hwndList=GetDlgItem(hwndDlg,IDC_ASSOCLIST); lvi.iSubItem=0; lvi.mask=LVIF_PARAM; EnterCriticalSection(&csAssocList); for(lvi.iItem=0;ListView_GetItem(hwndList,&lvi);++lvi.iItem) { assoc=(ASSOCDATA*)lvi.lParam; if(assoc==NULL) continue; /* groups */ fEnabled=ListView_GetCheckState(hwndList,lvi.iItem); SetAssocEnabled(assoc,fEnabled); /* re-register registery keys */ if(fEnabled?!EnsureAssocRegistered(assoc):!UnregisterAssoc(assoc)) { char *pszErr; pszErr=GetWinErrorDescription(GetLastError()); ShowInfoMessage(NIIF_ERROR,Translate("File Association Error"),Translate("There was an error writing to the registry to modify the file/url associations.\nReason: %s"),(pszErr!=NULL)?pszErr:Translate("Unknown")); mir_free(pszErr); /* does NULL check */ fRegFailed=TRUE; /* just show one time */ } } LeaveCriticalSection(&csAssocList); NotifyAssocChange(TRUE); PostMessage(hwndDlg,M_REFRESH_ICONS,0,0); if(fRegFailed) { } /* autostart */ { TCHAR *pszRunCmd; pszRunCmd=MakeRunCommand(TRUE,TRUE); fRegFailed=FALSE; if(pszRunCmd!=NULL) { fEnabled=IsDlgButtonChecked(hwndDlg,IDC_AUTOSTART); if(fEnabled?!AddRegRunEntry(_T("MirandaIM"),pszRunCmd):!RemoveRegRunEntry(_T("MirandaIM"),pszRunCmd)) { char *pszErr; pszErr=GetWinErrorDescription(GetLastError()); ShowInfoMessage(NIIF_ERROR,Translate("Autostart Error"),Translate("There was an error writing to the registry to modify the autostart list.\n\nReason: %s"),(pszErr!=NULL)?pszErr:Translate("Unknown")); mir_free(pszErr); /* does NULL check */ fRegFailed=TRUE; /* just show one time */ } mir_free(pszRunCmd); } } return TRUE; } } /* code */ break; } /* idFrom */ break; } /* WM_NOTIFY */ } return FALSE; } static int AssocListOptInit(WPARAM wParam,LPARAM lParam) { OPTIONSDIALOGPAGE odp; UNREFERENCED_PARAMETER(lParam); ZeroMemory(&odp,sizeof(odp)); odp.cbSize=sizeof(odp); odp.hInstance=hInst; odp.pszTemplate=MAKEINTRESOURCEA(IDD_OPT_ASSOCLIST); odp.position=900000100; /* network opts = 900000000 */ odp.ptszGroup=_T("Services"); /* autotranslated */ odp.ptszTitle=_T("Associations"); /* autotranslated */ odp.flags=ODPF_BOLDGROUPS|ODPF_EXPERTONLY|ODPF_TCHAR; odp.pfnDlgProc=AssocListOptDlgProc; CallService(MS_OPT_ADDPAGE,wParam,(LPARAM)&odp); return 0; } /************************* Misc ***********************************/ void InitAssocList(void) { /* Options */ INITCOMMONCONTROLSEX icc; icc.dwSize=sizeof(icc); icc.dwICC=ICC_LISTVIEW_CLASSES; InitCommonControlsEx(&icc); hHookOptInit=HookEvent(ME_OPT_INITIALISE,AssocListOptInit); /* Assoc List */ pAssocList=NULL; nAssocListCount=0; InitializeCriticalSection(&csAssocList); /* Services */ hServiceAddFile=CreateServiceFunction(MS_ASSOCMGR_ADDNEWFILETYPE,ServiceAddNewFileType); hServiceRemoveFile=CreateServiceFunction(MS_ASSOCMGR_REMOVEFILETYPE,ServiceRemoveFileType); hServiceAddUrl=CreateServiceFunction(MS_ASSOCMGR_ADDNEWURLTYPE,ServiceAddNewUrlType); hServiceRemoveUrl=CreateServiceFunction(MS_ASSOCMGR_REMOVEURLTYPE,ServiceRemoveUrlType); /* Notify Shell */ nNotifyTimerID=0; InitializeCriticalSection(&csNotifyTimer); /* register open-with app */ { TCHAR *pszAppFileName,*pszIconLoc,*pszRunCmd; pszIconLoc=MakeIconLocation(NULL,0); // miranda32.exe pszAppFileName=MakeAppFileName(TRUE); pszRunCmd=MakeRunCommand(TRUE,FALSE); if(pszAppFileName!=NULL && pszRunCmd!=NULL) AddRegOpenWith(pszAppFileName,FALSE,_T(MIRANDANAME),pszIconLoc,pszRunCmd,NULL,NULL,NULL); mir_free(pszRunCmd); /* does NULL check */ mir_free(pszAppFileName); /* does NULL check */ // assocmgr.dll pszAppFileName=MakeAppFileName(FALSE); pszRunCmd=MakeRunCommand(FALSE,TRUE); if(pszAppFileName!=NULL && pszRunCmd!=NULL) AddRegOpenWith(pszAppFileName,TRUE,_T(MIRANDANAME),pszIconLoc,pszRunCmd,DDEFILECMD,DDEAPP,DDETOPIC); mir_free(pszRunCmd); /* does NULL check */ mir_free(pszAppFileName); /* does NULL check */ mir_free(pszIconLoc); /* does NULL check */ } /* default items */ { FILETYPEDESC ftd; ftd.cbSize=sizeof(FILETYPEDESC); ftd.pszFileExt=".dat"; ftd.pszMimeType=NULL; ftd.ptszDescription=TranslateT("Miranda IM Database"); ftd.hInstance=hInst; ftd.nIconResID=IDI_MIRANDAFILE; ftd.ptszVerbDesc=NULL; ftd.pszService=NULL; ftd.flags=FTDF_DEFAULTDISABLED|FTDF_TCHAR; ServiceAddNewFileType(0,(LPARAM)&ftd); } } void UninitAssocList(void) { BYTE fOnlyWhileRunning; ASSOCDATA *assoc; int i; /* Options */ UnhookEvent(hHookOptInit); /* Services */ DestroyServiceFunction(hServiceAddFile); DestroyServiceFunction(hServiceRemoveFile); DestroyServiceFunction(hServiceAddUrl); DestroyServiceFunction(hServiceRemoveUrl); /* Assoc List */ fOnlyWhileRunning=DBGetContactSettingByte(NULL,"AssocMgr","OnlyWhileRunning",SETTING_ONLYWHILERUNNING_DEFAULT); for(i=0;ipszClassName); mir_free(assoc->pszDescription); mir_free(assoc->pszService); mir_free(assoc->pszFileExt); /* does NULL check */ mir_free(assoc->pszVerbDesc); /* does NULL check */ mir_free(assoc->pszMimeType); /* does NULL check */ } mir_free(pAssocList); DeleteCriticalSection(&csAssocList); /* Notify Shell */ if(fOnlyWhileRunning && nAssocListCount) NotifyAssocChange(TRUE); DeleteCriticalSection(&csNotifyTimer); /* unregister open-with app */ if(fOnlyWhileRunning) { TCHAR *pszAppFileName; // miranda32.exe pszAppFileName=MakeAppFileName(TRUE); if(pszAppFileName!=NULL) RemoveRegOpenWith(pszAppFileName); pszAppFileName=MakeAppFileName(FALSE); // assocmgr.dll if(pszAppFileName!=NULL) RemoveRegOpenWith(pszAppFileName); mir_free(pszAppFileName); /* does NULL check */ } }