/* * Rufus: The Reliable USB Formatting Utility * Standard Dialog Routines (Browse for folder, About, etc) * Copyright © 2011-2012 Pete Batard * * Based on zadig_stdlg.c, part of libwdi: http://libwdi.sf.net * * 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 3 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. If not, see . */ /* Memory leaks detection - define _CRTDBG_MAP_ALLOC as preprocessor macro */ #ifdef _CRTDBG_MAP_ALLOC #include #include #endif #include #include #include #include #include #include #include #include #include #include "rufus.h" #include "msapi_utf8.h" #include "registry.h" #include "resource.h" #include "license.h" /* The following is only available on Vista and later */ #if (_WIN32_WINNT >= 0x0600) static HRESULT (WINAPI *pSHCreateItemFromParsingName)(PCWSTR, IBindCtx*, REFIID, void **) = NULL; #endif #define INIT_VISTA_SHELL32 if (pSHCreateItemFromParsingName == NULL) { \ pSHCreateItemFromParsingName = (HRESULT (WINAPI *)(PCWSTR, IBindCtx*, REFIID, void **)) \ GetProcAddress(GetModuleHandleA("SHELL32"), "SHCreateItemFromParsingName"); \ } #define IS_VISTA_SHELL32_AVAILABLE (pSHCreateItemFromParsingName != NULL) // And this one is simply not available in MinGW32 static LPITEMIDLIST (WINAPI *pSHSimpleIDListFromPath)(PCWSTR pszPath) = NULL; #define INIT_XP_SHELL32 if (pSHSimpleIDListFromPath == NULL) { \ pSHSimpleIDListFromPath = (LPITEMIDLIST (WINAPI *)(PCWSTR)) \ GetProcAddress(GetModuleHandleA("SHELL32"), "SHSimpleIDListFromPath"); \ } /* Globals */ static HICON hMessageIcon = (HICON)INVALID_HANDLE_VALUE; static char* szMessageText = NULL; static char* szMessageTitle = NULL; enum WindowsVersion nWindowsVersion = WINDOWS_UNDEFINED; static HWND hBrowseEdit; static WNDPROC pOrgBrowseWndproc; static const SETTEXTEX friggin_microsoft_unicode_amateurs = {ST_DEFAULT, CP_UTF8}; static BOOL notification_is_question; static const notification_info* notification_more_info; static BOOL reg_commcheck = FALSE; /* * Detect Windows version */ enum WindowsVersion DetectWindowsVersion(void) { OSVERSIONINFO OSVersion; memset(&OSVersion, 0, sizeof(OSVERSIONINFO)); OSVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (GetVersionEx(&OSVersion) == 0) return WINDOWS_UNDEFINED; if (OSVersion.dwPlatformId != VER_PLATFORM_WIN32_NT) return WINDOWS_UNSUPPORTED; // See the Remarks section from http://msdn.microsoft.com/en-us/library/windows/desktop/ms724833.aspx if ((OSVersion.dwMajorVersion < 5) || ((OSVersion.dwMajorVersion == 5) && (OSVersion.dwMinorVersion == 0))) return WINDOWS_UNSUPPORTED; // Win2k or earlier if ((OSVersion.dwMajorVersion == 5) && (OSVersion.dwMinorVersion == 1)) return WINDOWS_XP; if ((OSVersion.dwMajorVersion == 5) && (OSVersion.dwMinorVersion == 2)) return WINDOWS_2003; if ((OSVersion.dwMajorVersion == 6) && (OSVersion.dwMinorVersion == 0)) return WINDOWS_VISTA; if ((OSVersion.dwMajorVersion == 6) && (OSVersion.dwMinorVersion == 1)) return WINDOWS_7; if ((OSVersion.dwMajorVersion == 6) && (OSVersion.dwMinorVersion == 2)) return WINDOWS_8; if ((OSVersion.dwMajorVersion > 6) || ((OSVersion.dwMajorVersion == 6) && (OSVersion.dwMinorVersion >= 3))) return WINDOWS_9; return WINDOWS_UNSUPPORTED; } /* * String array manipulation */ void StrArrayCreate(StrArray* arr, size_t initial_size) { if (arr == NULL) return; arr->Max = initial_size; arr->Index = 0; arr->Table = (char**)calloc(arr->Max, sizeof(char*)); if (arr->Table == NULL) uprintf("Could not allocate string array\n"); } void StrArrayAdd(StrArray* arr, const char* str) { if ((arr == NULL) || (arr->Table == NULL)) return; if (arr->Index == arr->Max) { arr->Max *= 2; arr->Table = (char**)realloc(arr->Table, arr->Max*sizeof(char*)); if (arr->Table == NULL) { uprintf("Could not reallocate string array\n"); return; } } arr->Table[arr->Index] = safe_strdup(str); if (arr->Table[arr->Index++] == NULL) { uprintf("Could not store string in array\n"); } } void StrArrayClear(StrArray* arr) { size_t i; if ((arr == NULL) || (arr->Table == NULL)) return; for (i=0; iIndex; i++) { safe_free(arr->Table[i]); } arr->Index = 0; } void StrArrayDestroy(StrArray* arr) { StrArrayClear(arr); if (arr != NULL) safe_free(arr->Table); } /* * Retrieve the SID of the current user. The returned PSID must be freed by the caller using LocalFree() */ static PSID GetSID(void) { TOKEN_USER* tu = NULL; DWORD len; HANDLE token; PSID ret = NULL; char* psid_string = NULL; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { uprintf("OpenProcessToken failed: %s\n", WindowsErrorString()); return NULL; } if (!GetTokenInformation(token, TokenUser, tu, 0, &len)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { uprintf("GetTokenInformation (pre) failed: %s\n", WindowsErrorString()); return NULL; } tu = (TOKEN_USER*)calloc(1, len); } if (tu == NULL) { return NULL; } if (GetTokenInformation(token, TokenUser, tu, len, &len)) { /* * now of course, the interesting thing is that if you return tu->User.Sid * but free tu, the PSID pointer becomes invalid after a while. * The workaround? Convert to string then back to PSID */ if (!ConvertSidToStringSidA(tu->User.Sid, &psid_string)) { uprintf("Unable to convert SID to string: %s\n", WindowsErrorString()); ret = NULL; } else { if (!ConvertStringSidToSidA(psid_string, &ret)) { uprintf("Unable to convert string back to SID: %s\n", WindowsErrorString()); ret = NULL; } // MUST use LocalFree() LocalFree(psid_string); } } else { ret = NULL; uprintf("GetTokenInformation (real) failed: %s\n", WindowsErrorString()); } free(tu); return ret; } /* * We need a sub-callback to read the content of the edit box on exit and update * our path, else if what the user typed does match the selection, it is discarded. * Talk about a convoluted way of producing an intuitive folder selection dialog */ INT CALLBACK BrowseDlgCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_DESTROY: GetWindowTextU(hBrowseEdit, szFolderPath, sizeof(szFolderPath)); break; } return (INT)CallWindowProc(pOrgBrowseWndproc, hDlg, message, wParam, lParam); } /* * Main BrowseInfo callback to set the initial directory and populate the edit control */ INT CALLBACK BrowseInfoCallback(HWND hDlg, UINT message, LPARAM lParam, LPARAM pData) { char dir[MAX_PATH]; wchar_t* wpath; LPITEMIDLIST pidl; switch(message) { case BFFM_INITIALIZED: pOrgBrowseWndproc = (WNDPROC)SetWindowLongPtr(hDlg, GWLP_WNDPROC, (LONG_PTR)BrowseDlgCallback); // Windows hides the full path in the edit box by default, which is bull. // Get a handle to the edit control to fix that hBrowseEdit = FindWindowExA(hDlg, NULL, "Edit", NULL); SetWindowTextU(hBrowseEdit, szFolderPath); SetFocus(hBrowseEdit); // On XP, BFFM_SETSELECTION can't be used with a Unicode Path in SendMessageW // or a pidl (at least with MinGW) => must use SendMessageA if (nWindowsVersion <= WINDOWS_XP) { SendMessageLU(hDlg, BFFM_SETSELECTION, (WPARAM)TRUE, szFolderPath); } else { // On Windows 7, MinGW only properly selects the specified folder when using a pidl wpath = utf8_to_wchar(szFolderPath); pidl = (*pSHSimpleIDListFromPath)(wpath); safe_free(wpath); // NB: see http://connect.microsoft.com/VisualStudio/feedback/details/518103/bffm-setselection-does-not-work-with-shbrowseforfolder-on-windows-7 // for details as to why we send BFFM_SETSELECTION twice. SendMessageW(hDlg, BFFM_SETSELECTION, (WPARAM)FALSE, (LPARAM)pidl); Sleep(100); PostMessageW(hDlg, BFFM_SETSELECTION, (WPARAM)FALSE, (LPARAM)pidl); } break; case BFFM_SELCHANGED: // Update the status if (SHGetPathFromIDListU((LPITEMIDLIST)lParam, dir)) { SendMessageLU(hDlg, BFFM_SETSTATUSTEXT, 0, dir); SetWindowTextU(hBrowseEdit, dir); } break; } return 0; } /* * Browse for a folder and update the folder edit box * Will use the newer IFileOpenDialog if *compiled* for Vista or later */ void BrowseForFolder(void) { BROWSEINFOW bi; LPITEMIDLIST pidl; #if (_WIN32_WINNT >= 0x0600) // Vista and later WCHAR *wpath; size_t i; HRESULT hr; IShellItem *psi = NULL; IShellItem *si_path = NULL; // Automatically freed IFileOpenDialog *pfod = NULL; WCHAR *fname; char* tmp_path = NULL; // Even if we have Vista support with the compiler, // it does not mean we have the Vista API available INIT_VISTA_SHELL32; if (IS_VISTA_SHELL32_AVAILABLE) { hr = CoCreateInstance(&CLSID_FileOpenDialog, NULL, CLSCTX_INPROC, &IID_IFileOpenDialog, (LPVOID)&pfod); if (FAILED(hr)) { uprintf("CoCreateInstance for FileOpenDialog failed: error %X\n", hr); pfod = NULL; // Just in case goto fallback; } hr = pfod->lpVtbl->SetOptions(pfod, FOS_PICKFOLDERS); if (FAILED(hr)) { uprintf("Failed to set folder option for FileOpenDialog: error %X\n", hr); goto fallback; } // Set the initial folder (if the path is invalid, will simply use last) wpath = utf8_to_wchar(szFolderPath); // The new IFileOpenDialog makes us split the path fname = NULL; if ((wpath != NULL) && (wcslen(wpath) >= 1)) { for (i=wcslen(wpath)-1; i!=0; i--) { if (wpath[i] == L'\\') { wpath[i] = 0; fname = &wpath[i+1]; break; } } } hr = (*pSHCreateItemFromParsingName)(wpath, NULL, &IID_IShellItem, (LPVOID)&si_path); if (SUCCEEDED(hr)) { if (wpath != NULL) { hr = pfod->lpVtbl->SetFolder(pfod, si_path); } if (fname != NULL) { hr = pfod->lpVtbl->SetFileName(pfod, fname); } } safe_free(wpath); hr = pfod->lpVtbl->Show(pfod, hMainDialog); if (SUCCEEDED(hr)) { hr = pfod->lpVtbl->GetResult(pfod, &psi); if (SUCCEEDED(hr)) { psi->lpVtbl->GetDisplayName(psi, SIGDN_FILESYSPATH, &wpath); tmp_path = wchar_to_utf8(wpath); CoTaskMemFree(wpath); if (tmp_path == NULL) { uprintf("Could not convert path\n"); } else { safe_strcpy(szFolderPath, MAX_PATH, tmp_path); safe_free(tmp_path); } } else { uprintf("Failed to set folder option for FileOpenDialog: error %X\n", hr); } } else if ((hr & 0xFFFF) != ERROR_CANCELLED) { // If it's not a user cancel, assume the dialog didn't show and fallback uprintf("Could not show FileOpenDialog: error %X\n", hr); goto fallback; } pfod->lpVtbl->Release(pfod); return; } fallback: if (pfod != NULL) { pfod->lpVtbl->Release(pfod); } #endif INIT_XP_SHELL32; memset(&bi, 0, sizeof(BROWSEINFOW)); bi.hwndOwner = hMainDialog; bi.lpszTitle = L"Please select the installation folder:"; bi.lpfn = BrowseInfoCallback; // BIF_NONEWFOLDERBUTTON = 0x00000200 is unknown on MinGW bi.ulFlags = BIF_RETURNFSANCESTORS | BIF_RETURNONLYFSDIRS | BIF_DONTGOBELOWDOMAIN | BIF_EDITBOX | 0x00000200; pidl = SHBrowseForFolderW(&bi); if (pidl != NULL) { CoTaskMemFree(pidl); } } /* * read or write I/O to a file * buffer is allocated by the procedure. path is UTF-8 */ BOOL FileIO(BOOL save, char* path, char** buffer, DWORD* size) { SECURITY_ATTRIBUTES s_attr, *ps = NULL; SECURITY_DESCRIPTOR s_desc; PSID sid = NULL; HANDLE handle; BOOL r; BOOL ret = FALSE; // Change the owner from admin to regular user sid = GetSID(); if ( (sid != NULL) && InitializeSecurityDescriptor(&s_desc, SECURITY_DESCRIPTOR_REVISION) && SetSecurityDescriptorOwner(&s_desc, sid, FALSE) ) { s_attr.nLength = sizeof(SECURITY_ATTRIBUTES); s_attr.bInheritHandle = FALSE; s_attr.lpSecurityDescriptor = &s_desc; ps = &s_attr; } else { uprintf("Could not set security descriptor: %s\n", WindowsErrorString()); } if (!save) { *buffer = NULL; } handle = CreateFileU(path, save?GENERIC_WRITE:GENERIC_READ, FILE_SHARE_READ, ps, save?CREATE_ALWAYS:OPEN_EXISTING, 0, NULL); if (handle == INVALID_HANDLE_VALUE) { uprintf("Could not %s file '%s'\n", save?"create":"open", path); goto out; } if (save) { r = WriteFile(handle, *buffer, *size, size, NULL); } else { *size = GetFileSize(handle, NULL); *buffer = (char*)malloc(*size); if (*buffer == NULL) { uprintf("Could not allocate buffer for reading file\n"); goto out; } r = ReadFile(handle, *buffer, *size, size, NULL); } if (!r) { uprintf("I/O Error: %s\n", WindowsErrorString()); goto out; } PrintStatus(0, TRUE, "%s '%s'", save?"Saved":"Opened", path); ret = TRUE; out: CloseHandle(handle); if (!ret) { // Only leave a buffer allocated if successful *size = 0; if (!save) { safe_free(*buffer); } } return ret; } /* * Return the UTF8 path of a file selected through a load or save dialog * Will use the newer IFileOpenDialog if *compiled* for Vista or later * All string parameters are UTF-8 */ char* FileDialog(BOOL save, char* path, char* filename, char* ext, char* ext_desc) { DWORD tmp; OPENFILENAMEA ofn; char selected_name[MAX_PATH]; char* ext_string = NULL; size_t i, ext_strlen; BOOL r; char* filepath = NULL; #if (_WIN32_WINNT >= 0x0600) // Vista and later HRESULT hr = FALSE; IFileDialog *pfd = NULL; IShellItem *psiResult; COMDLG_FILTERSPEC filter_spec[2]; char* ext_filter; wchar_t *wpath = NULL, *wfilename = NULL; IShellItem *si_path = NULL; // Automatically freed INIT_VISTA_SHELL32; if (IS_VISTA_SHELL32_AVAILABLE) { // Setup the file extension filter table ext_filter = (char*)malloc(strlen(ext)+3); if (ext_filter != NULL) { safe_sprintf(ext_filter, strlen(ext)+3, "*.%s", ext); filter_spec[0].pszSpec = utf8_to_wchar(ext_filter); safe_free(ext_filter); filter_spec[0].pszName = utf8_to_wchar(ext_desc); filter_spec[1].pszSpec = L"*.*"; filter_spec[1].pszName = L"All files"; } hr = CoCreateInstance(save?&CLSID_FileSaveDialog:&CLSID_FileOpenDialog, NULL, CLSCTX_INPROC, &IID_IFileDialog, (LPVOID)&pfd); if (FAILED(hr)) { uprintf("CoCreateInstance for FileOpenDialog failed: error %X\n", hr); pfd = NULL; // Just in case goto fallback; } // Set the file extension filters pfd->lpVtbl->SetFileTypes(pfd, 2, filter_spec); // Set the default directory wpath = utf8_to_wchar(path); hr = (*pSHCreateItemFromParsingName)(wpath, NULL, &IID_IShellItem, (LPVOID) &si_path); if (SUCCEEDED(hr)) { pfd->lpVtbl->SetFolder(pfd, si_path); } safe_free(wpath); // Set the default filename wfilename = utf8_to_wchar(filename); if (wfilename != NULL) { pfd->lpVtbl->SetFileName(pfd, wfilename); } // Display the dialog hr = pfd->lpVtbl->Show(pfd, hMainDialog); // Cleanup safe_free(wfilename); safe_free(filter_spec[0].pszSpec); safe_free(filter_spec[0].pszName); if (SUCCEEDED(hr)) { // Obtain the result of the user's interaction with the dialog. hr = pfd->lpVtbl->GetResult(pfd, &psiResult); if (SUCCEEDED(hr)) { hr = psiResult->lpVtbl->GetDisplayName(psiResult, SIGDN_FILESYSPATH, &wpath); if (SUCCEEDED(hr)) { filepath = wchar_to_utf8(wpath); CoTaskMemFree(wpath); } psiResult->lpVtbl->Release(psiResult); } } else if ((hr & 0xFFFF) != ERROR_CANCELLED) { // If it's not a user cancel, assume the dialog didn't show and fallback uprintf("Could not show FileOpenDialog: error %X\n", hr); goto fallback; } pfd->lpVtbl->Release(pfd); return filepath; } fallback: if (pfd != NULL) { pfd->lpVtbl->Release(pfd); } #endif memset(&ofn, 0, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hMainDialog; // File name safe_strcpy(selected_name, MAX_PATH, filename); ofn.lpstrFile = selected_name; ofn.nMaxFile = MAX_PATH; // Set the file extension filters ext_strlen = strlen(ext_desc) + 2*strlen(ext) + sizeof(" (*.)\0*.\0All Files (*.*)\0*.*\0\0"); ext_string = (char*)malloc(ext_strlen); safe_sprintf(ext_string, ext_strlen, "%s (*.%s)\r*.%s\rAll Files (*.*)\r*.*\r\0", ext_desc, ext, ext); // Microsoft could really have picked a better delimiter! for (i=0; icode) { case EN_LINK: enl = (ENLINK*) lParam; if (enl->msg == WM_LBUTTONUP) { tr.lpstrText = wUrl; tr.chrg.cpMin = enl->chrg.cpMin; tr.chrg.cpMax = enl->chrg.cpMax; SendMessageW(enl->nmhdr.hwndFrom, EM_GETTEXTRANGE, 0, (LPARAM)&tr); ShellExecuteW(hDlg, L"open", wUrl, NULL, NULL, SW_SHOWNORMAL); } break; } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; case IDC_ABOUT_LICENSE: DialogBoxA(hMainInstance, MAKEINTRESOURCEA(IDD_LICENSE), hDlg, LicenseCallback); break; case IDC_ABOUT_UPDATES: DialogBoxA(hMainInstance, MAKEINTRESOURCEA(IDD_UPDATE_POLICY), hDlg, UpdateCallback); break; } break; } return (INT_PTR)FALSE; } INT_PTR CreateAboutBox(void) { return DialogBoxA(hMainInstance, MAKEINTRESOURCEA(IDD_ABOUTBOX), hMainDialog, AboutCallback); } /* * We use our own MessageBox for notifications to have greater control (center, no close button, etc) */ INT_PTR CALLBACK NotificationCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT loc; int i; // Prevent resizing static LRESULT disabled[9] = { HTLEFT, HTRIGHT, HTTOP, HTBOTTOM, HTSIZE, HTTOPLEFT, HTTOPRIGHT, HTBOTTOMLEFT, HTBOTTOMRIGHT }; static HBRUSH white_brush, separator_brush; switch (message) { case WM_INITDIALOG: white_brush = CreateSolidBrush(WHITE); separator_brush = CreateSolidBrush(SEPARATOR_GREY); CenterDialog(hDlg); // Change the default icon if (Static_SetIcon(GetDlgItem(hDlg, IDC_NOTIFICATION_ICON), hMessageIcon) == 0) { uprintf("Could not set dialog icon\n"); } // Set the dialog title if (szMessageTitle != NULL) { SetWindowTextA(hDlg, szMessageTitle); } // Enable/disable the buttons and set text if (!notification_is_question) { SetWindowTextA(GetDlgItem(hDlg, IDNO), "Close"); } else { ShowWindow(GetDlgItem(hDlg, IDYES), SW_SHOW); } if ((notification_more_info != NULL) && (notification_more_info->callback != NULL)) { ShowWindow(GetDlgItem(hDlg, IDC_MORE_INFO), SW_SHOW); } // Set the control text if (szMessageText != NULL) { SetWindowTextA(GetDlgItem(hDlg, IDC_NOTIFICATION_TEXT), szMessageText); } return (INT_PTR)TRUE; case WM_CTLCOLORSTATIC: // Change the background colour for static text and icon SetBkMode((HDC)wParam, TRANSPARENT); if ((HWND)lParam == GetDlgItem(hDlg, IDC_NOTIFICATION_LINE)) { return (INT_PTR)separator_brush; } return (INT_PTR)white_brush; case WM_NCHITTEST: // Check coordinates to prevent resize actions loc = DefWindowProc(hDlg, message, wParam, lParam); for(i = 0; i < 9; i++) { if (loc == disabled[i]) { return (INT_PTR)TRUE; } } return (INT_PTR)FALSE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: case IDCANCEL: case IDYES: case IDNO: EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; case IDC_MORE_INFO: if (notification_more_info != NULL) DialogBoxA(hMainInstance, MAKEINTRESOURCEA(notification_more_info->id), hDlg, notification_more_info->callback); break; } break; } return (INT_PTR)FALSE; } /* * Display a custom notification */ BOOL Notification(int type, const notification_info* more_info, char* title, char* format, ...) { BOOL ret; va_list args; szMessageText = (char*)malloc(MAX_PATH); if (szMessageText == NULL) return FALSE; szMessageTitle = title; va_start(args, format); safe_vsnprintf(szMessageText, MAX_PATH-1, format, args); va_end(args); szMessageText[MAX_PATH-1] = 0; notification_more_info = more_info; notification_is_question = FALSE; switch(type) { case MSG_WARNING: hMessageIcon = LoadIcon(NULL, IDI_WARNING); break; case MSG_ERROR: hMessageIcon = LoadIcon(NULL, IDI_ERROR); break; case MSG_QUESTION: hMessageIcon = LoadIcon(NULL, IDI_QUESTION); notification_is_question = TRUE; break; case MSG_INFO: default: hMessageIcon = LoadIcon(NULL, IDI_INFORMATION); break; } ret = (DialogBox(hMainInstance, MAKEINTRESOURCE(IDD_NOTIFICATION), hMainDialog, NotificationCallback) == IDYES); safe_free(szMessageText); return ret; } static struct { HWND hTip; // Tooltip handle HWND hCtrl; // Handle of the control the tooltip belongs to WNDPROC original_proc; LPWSTR wstring; } ttlist[MAX_TOOLTIPS] = { {0} }; INT_PTR CALLBACK TooltipCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { LPNMTTDISPINFOW lpnmtdi; int i = MAX_TOOLTIPS; // Make sure we have an original proc for (i=0; icode) { case TTN_GETDISPINFOW: lpnmtdi = (LPNMTTDISPINFOW)lParam; lpnmtdi->lpszText = ttlist[i].wstring; SendMessage(hDlg, TTM_SETMAXTIPWIDTH, 0, 300); return (INT_PTR)TRUE; } break; } return CallWindowProc(ttlist[i].original_proc, hDlg, message, wParam, lParam); } /* * Create a tooltip for the control passed as first parameter * duration sets the duration in ms. Use -1 for default * message is an UTF-8 string */ BOOL CreateTooltip(HWND hControl, const char* message, int duration) { TOOLINFOW toolInfo = {0}; int i; if ( (hControl == NULL) || (message == NULL) ) { return FALSE; } // Destroy existing tooltip if any DestroyTooltip(hControl); // Find an empty slot for (i=0; ilpVtbl->SetProgressState(ptbl, hMainDialog, tbpFlags)); } BOOL SetTaskbarProgressValue(ULONGLONG ullCompleted, ULONGLONG ullTotal) { if (ptbl == NULL) return FALSE; return !FAILED(ptbl->lpVtbl->SetProgressValue(ptbl, hMainDialog, ullCompleted, ullTotal)); } #pragma pop_macro("INTERFACE") /* * Update policy and settings dialog callback */ INT_PTR CALLBACK UpdateCallback(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { HWND hPolicy; static HWND hFrequency, hBeta; DWORD frequency = -1; switch (message) { case WM_INITDIALOG: CenterDialog(hDlg); hFrequency = GetDlgItem(hDlg, IDC_UPDATE_FREQUENCY); IGNORE_RETVAL(ComboBox_SetItemData(hFrequency, ComboBox_AddStringU(hFrequency, "Disabled"), -1)); IGNORE_RETVAL(ComboBox_SetItemData(hFrequency, ComboBox_AddStringU(hFrequency, "Daily (Default)"), 86400)); IGNORE_RETVAL(ComboBox_SetItemData(hFrequency, ComboBox_AddStringU(hFrequency, "Weekly"), 604800)); IGNORE_RETVAL(ComboBox_SetItemData(hFrequency, ComboBox_AddStringU(hFrequency, "Monthly"), 2629800)); IGNORE_RETVAL(ComboBox_SetCurSel(hFrequency, 1)); hBeta = GetDlgItem(hDlg, IDC_INCLUDE_BETAS); IGNORE_RETVAL(ComboBox_AddStringU(hBeta, "Yes")); IGNORE_RETVAL(ComboBox_AddStringU(hBeta, "No")); IGNORE_RETVAL(ComboBox_SetCurSel(hBeta, 1)); hPolicy = GetDlgItem(hDlg, IDC_POLICY); SendMessage(hPolicy, EM_AUTOURLDETECT, 1, 0); SendMessageA(hPolicy, EM_SETTEXTEX, (WPARAM)&friggin_microsoft_unicode_amateurs, (LPARAM)update_policy); SendMessage(hPolicy, EM_SETSEL, -1, -1); SendMessage(hPolicy, EM_SETEVENTMASK, 0, ENM_LINK); SendMessageA(hPolicy, EM_SETBKGNDCOLOR, 0, (LPARAM)GetSysColor(COLOR_BTNFACE)); break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDCLOSE: case IDCANCEL: EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; case IDC_UPDATE_FREQUENCY: if (HIWORD(wParam) != CBN_SELCHANGE) break; WriteRegistryKey32(REGKEY_UPDATE_INTERVAL, (DWORD)ComboBox_GetItemData(hFrequency, ComboBox_GetCurSel(hFrequency))); return (INT_PTR)TRUE; case IDC_INCLUDE_BETAS: if (HIWORD(wParam) != CBN_SELCHANGE) break; SetRegistryKeyBool(REGKEY_INCLUDE_BETAS, ComboBox_GetCurSel(hBeta) == 0); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; } /* * Initial update check setup */ BOOL SetUpdateCheck(void) { BOOL enable_updates; DWORD commcheck = GetTickCount(); notification_info more_info = { IDD_UPDATE_POLICY, UpdateCallback }; // Test if we have access to the registry. If not, forget it. WriteRegistryKey32(REGKEY_COMM_CHECK, commcheck); if (ReadRegistryKey32(REGKEY_COMM_CHECK) != commcheck) return FALSE; reg_commcheck = TRUE; // If the update interval is not set, this is the first time we run so prompt the user if (ReadRegistryKey32(REGKEY_UPDATE_INTERVAL) == 0) { enable_updates = Notification(MSG_QUESTION, &more_info, APPLICATION_NAME " updates", "Do you want to allow " APPLICATION_NAME " to check for updates?\n"); if (!enable_updates) { WriteRegistryKey32(REGKEY_UPDATE_INTERVAL, -1); // large enough return FALSE; } // If the user hasn't set the interval in the dialog, set to default if ( (ReadRegistryKey32(REGKEY_UPDATE_INTERVAL) == 0) || ((ReadRegistryKey32(REGKEY_UPDATE_INTERVAL) == -1) && enable_updates) ) WriteRegistryKey32(REGKEY_UPDATE_INTERVAL, 86400); } // TODO: check for lastcheck + interval & launch the background thread here? // TODO: make sure we check for updates if user just accepted return TRUE; }