/* '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 "stdafx.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 db_get_b(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); db_set_b(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( PathToRelativeT(szBuf, szDLL)) { mir_snprintf(szSetting, SIZEOF(szSetting), "module_%s", assoc->pszClassName); db_set_ts(NULL, "AssocMgr", szSetting, szDLL); } } static void DeleteAssocEnabledSetting(const ASSOCDATA *assoc) { char szSetting[MAXMODULELABELLENGTH]; mir_snprintf(szSetting, SIZEOF(szSetting), "enabled_%s", assoc->pszClassName); db_unset(NULL, "AssocMgr", szSetting); /* dll name for uninstall */ mir_snprintf(szSetting, SIZEOF(szSetting), "module_%s", assoc->pszClassName); db_unset(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), _T("%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; fIsUrl = (assoc->pszFileExt == NULL); fUseMainCmdLine = (assoc->pszService == NULL); /* class */ TCHAR *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; fIsUrl = (assoc->pszFileExt == NULL); fUseMainCmdLine = (assoc->pszService == NULL); /* class might have been registered by another instance */ TCHAR *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; /* is already in list? */ EnterCriticalSection(&csAssocList); int 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; /* find index */ EnterCriticalSection(&csAssocList); int 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)cbSize < sizeof(FILETYPEDESC)) return 1; if(ftd->pszFileExt == NULL || ftd->pszFileExt[0] != '.') return 2; char *pszFileExt = mir_strdup(ftd->pszFileExt); char *pszClassName = MakeFileClassName(ftd->pszFileExt); if(pszFileExt!= NULL && pszClassName!= NULL) { TCHAR *pszVerbDesc = s2t(ftd->ptszVerbDesc, ftd->flags&FTDF_UNICODE, TRUE); /* does NULL check */ char *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_PTR ServiceRemoveFileType(WPARAM, LPARAM lParam) { if ((char*)lParam == NULL) return 2; char *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_PTR ServiceAddNewUrlType(WPARAM, LPARAM lParam) { const URLTYPEDESC *utd = (URLTYPEDESC*)lParam; if(utd->cbSize < sizeof(URLTYPEDESC)) return 1; if(utd->pszService == NULL) return 2; if(utd->pszProtoPrefix == NULL || utd->pszProtoPrefix[mir_strlen(utd->pszProtoPrefix)-1]!= ':') return 2; char *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_PTR ServiceRemoveUrlType(WPARAM, LPARAM lParam) { if ((char*)lParam == NULL) return 2; char *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_PTR *res) { void *pvParam; char *pszService; /* find it in list */ EnterCriticalSection(&csAssocList); int index = FindAssocItem(pszClassName); if(index == -1) { LeaveCriticalSection(&csAssocList); return FALSE; } ASSOCDATA *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_PTR InvokeFileHandler(const TCHAR *pszFileName) { char *pszClassName, *pszFileExt; INT_PTR res = CALLSERVICE_NOTFOUND; /* find extension */ TCHAR *p = (TCHAR*)_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_PTR InvokeUrlHandler(const TCHAR *pszUrl) { char *pszClassName, *pszProtoPrefix, *p; INT_PTR 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_PTR CALLBACK AssocListOptDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_INITDIALOG: { HIMAGELIST himl; LVITEM lvi; ASSOCDATA *assoc; int i; TranslateDialogDefault(hwndDlg); CoInitialize(NULL); HWND hwndList = GetDlgItem(hwndDlg, IDC_ASSOCLIST); ListView_SetUnicodeFormat(hwndList, TRUE); 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 = TranslateT("URLs on websites"); lvi.iItem = ListView_InsertItem(hwndList, &lvi); if(lvi.iItem!= -1) { ListView_InsertGroup(hwndList, lvi.iItem, &lvg); lvg.iGroupId = 1; lvg.pszHeader = TranslateT("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)db_get_b(NULL, "AssocMgr", "OnlyWhileRunning", SETTING_ONLYWHILERUNNING_DEFAULT) ? BST_CHECKED : BST_UNCHECKED); /* autostart */ { TCHAR *pszRunCmd; pszRunCmd = MakeRunCommand(TRUE, TRUE); if(pszRunCmd!= NULL) { CheckDlgButton(hwndDlg, IDC_AUTOSTART, IsRegRunEntry(_T("MirandaNG"), pszRunCmd) ? BST_CHECKED : BST_UNCHECKED); mir_free(pszRunCmd); } } return TRUE; } case WM_SETTINGCHANGE: case M_REFRESH_ICONS: { LVITEM lvi; ASSOCDATA *assoc; HWND hwndList = GetDlgItem(hwndDlg, IDC_ASSOCLIST); HIMAGELIST 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) { lParam = (LPARAM)GetDlgItem(hwndDlg, IDC_MISCLABEL); HBRUSH hBrush = (HBRUSH)SendMessage(hwndDlg, msg, wParam, lParam); COLORREF clr; HTHEME hTheme = GetWindowTheme((HWND)lParam); if(hTheme!= NULL && !GetThemeColor(hTheme, BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, &clr)) { SetBkMode((HDC)wParam, TRANSPARENT); SetTextColor((HDC)wParam, clr); } return (BOOL)hBrush; } break; case WM_NCDESTROY: CoUninitialize(); return TRUE; case WM_COMMAND: /* enable apply */ PostMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); break; case WM_NOTIFYFORMAT: SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, NFR_UNICODE); return TRUE; 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) { SetWindowLongPtr(hwndDlg, DWLP_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); SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); /* eat it */ return TRUE; } break; } } break; } break; case 0: switch(nmhdr->code) { case PSN_APPLY: { LVITEM lvi; BOOL fEnabled, fRegFailed = FALSE; ASSOCDATA *assoc; /* only while running */ db_set_b(NULL, "AssocMgr", "OnlyWhileRunning", (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_ONLYWHILERUNNING)!= 0)); /* save enabled assoc items */ HWND 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 = 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); /* autostart */ { TCHAR *pszRunCmd = MakeRunCommand(TRUE, TRUE); fRegFailed = FALSE; if(pszRunCmd!= NULL) { fEnabled = IsDlgButtonChecked(hwndDlg, IDC_AUTOSTART); if(fEnabled?!AddRegRunEntry(_T("MirandaNG"), pszRunCmd):!RemoveRegRunEntry(_T("MirandaNG"), 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) { OPTIONSDIALOGPAGE odp = { 0 }; odp.hInstance = hInst; odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ASSOCLIST); odp.position = 900000100; /* network opts = 900000000 */ odp.pszGroup = LPGEN("Services"); /* autotranslated */ odp.pszTitle = LPGEN("Associations"); /* autotranslated */ odp.flags = ODPF_BOLDGROUPS; odp.pfnDlgProc = AssocListOptDlgProc; Options_AddPage(wParam, &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 NG 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; /* Options */ UnhookEvent(hHookOptInit); /* Services */ DestroyServiceFunction(hServiceAddFile); DestroyServiceFunction(hServiceRemoveFile); DestroyServiceFunction(hServiceAddUrl); DestroyServiceFunction(hServiceRemoveUrl); /* Assoc List */ fOnlyWhileRunning = db_get_b(NULL, "AssocMgr", "OnlyWhileRunning", SETTING_ONLYWHILERUNNING_DEFAULT); for(int 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 */ } }