//*************************************************************************** // // Library: // FM_UTILS.DLL - version 1.1a // // Purpose: // FM_UTILS is a File Manager extension DLL. An extension DLL adds // a menu to File Manager, contains an entry point that processes menu // commands and notification messages sent by File Manager, and // queries data and information about the File Manager windows. The // purpose of an extension DLL is to add administration support // features to File Manager, for example, file and disk utilities. // Up to five extension DLLs may be installed at any one time. // // FM_UTILS adds a menu (called "Utilities" by default) to File Manager // and processes all the messages that are sent by File Manager to an // extension DLL. In order to retrieve any information, it sends // messages to File Manager. // // Usage: // File Manager installs the extensions that have entries in the // [AddOns] section of the WINFILE.INI initialization file. An entry // consists of a tag and a value. To load FM_UTILS.DLL as a File // Manager extension, add the following to WINFILE.INI (assuming the // DLL resides in c:\win\system): // // [AddOns] // FM_Utils Extension=fm_utils.dll // // In addition, a section specific to this Extension DLL is added to // WINFILE.INI. There are several elements to this section: // For each utility to appear on the menu, there is a 'UserProgn=' tag, // and a 'UserDescn=' tag. Comments (preceded by semicolons) are optional. // You may have up to twenty separate utilities specified. // You can also specify up to five utilities to load at the same time as // FileMan, and exit when you close FileMan. You use the 'PreLoadn=' // tag for this. All of the utilities are started with the SW_SHOWMINNOACTIVE // setting (minimized, but not activated). // And, you can specify the name of the added menu. If you choose not to, // the default is "&Utilities". This provides foreign language support, // as well as allowing you to use a very short name, to keep the menu bar // from growing too long. The tag to use is 'UserMenuName='. // // [FM_Utils Extension] // ;Name the menu // UserMenuName=&Util // ;add Upper Deck Editor to menu // UserDesc1=&Edit File(s) // UserProg1=D:\UDE\UDE.EXE // ;add V. Buerg's LIST to menu // UserDesc2=&View File(s) // UserProg2=LIST.PIF // ;PreLoad Trash Can // PreLoad1=TRASH.EXE // // Revision History: // 1.0 - March '92 - initial release // // 1.1 - April '92 - added ability to PreLoad utilities. This seems even // more popular than the original DLL. // // 1.1a - August '92 - added ability to specify menu name; changed // SendMessage(...WM_CLOSE...) to PostMessage(...WM_CLOSE...) // since it seemed to make more sense. // // Notes: // Portions of the base code for this utility is based on, and borrowed // from, the XTENSION sample code provided with the Microsoft Windows // 3.1 SDK. // // FM_UTILS attempts to close any auto-started utilities by posting a // WM_CLOSE to it's main window. There's no guarantee that this method // will work for *all* programs. If you know of a better way to handle // this situation, I'd be *really* happy to hear about it. // // While it's not properly documented anywhere, it seems that the maximum // command line to pass to WinExec() is 128 characters. Since we have to // pass in complete pathnames for all files in order for this to work, // we may not be able to pass *all* selected file names. // // Copyright (c) 1992 by Brad P. Smith - all rights reserved // // snail: R.R. #2 - 777 Crozier Rd. // Oxford Mills, Ontario, CANADA // K0G 1S0 // E-Mail: 'B.P.Smith' on BIX, or 'smithb@cognos.com' on Internet // // This utility DLL is FreeWare. Don't let *anybody* charge you // for it. // However, I would *really* appreciate hearing from you if you find // FM_UTILS useful. Drop me a postcard or E-mail. I could use the moral // support. :-) // //*************************************************************************** #include #include #include #include #include "fm_utils.h" //******************** Global Variables HANDLE ghDllInst; // DLL's instance handle HMENU ghMenu; // Extension's menu handle WORD gwMenuDelta; // Delta for extension's menu items USERPROG UserProg[ NUM_USERPROGS ]; PRELOADPROG PreLoadProg[ NUM_PRELOADPROGS ]; char szTempBuffer[ PATH_NAME_LEN ]; //*************************************************************************** // // LibMain() // // Purpose: // LibMain is called by LibEntry. LibEntry is called by Windows // when the DLL is loaded. The LibEntry routine is provided // in the LIBENTRY.OBJ in the SDK Link Libraries disk. (The // source LIBENTRY.ASM is also provided.) // // LibEntry initializes the DLL's heap if a HEAPSIZE value is // specified in the DLL's DEF file. After this, LibEntry calls // LibMain. The LibMain function below satisfies that call. // // The LibMain function should perform additional initialization // tasks required by the DLL. In this DLL, no initialization // tasks are required; only the DLL's instance handle is saved. // LibMain should return a value of TRUE if the initialization is // successful. // // Parameters: // hLibInst - DLLs instance handle // wDataSeg - Data segment // cbHeapSize - Size of the DLL's heap // lpszCmdLine - Command line // // Return Value: // TRUE if the initialization is successful; FALSE otherwise. // //*************************************************************************** int CALLBACK LibMain( HANDLE hLibInst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) { ghDllInst = hLibInst; return TRUE; } //*************************************************************************** // // WEP() // // Purpose: // Performs cleanup tasks when the .DLL is unloaded. The WEP() is // called automatically by Windows when the DLL is unloaded. // // Make sure that the WEP() is @1 RESIDENTNAME in the EXPORTS // section of the .DEF file. This ensures that the WEP() can // be called as quickly as possible. Incidently, this is why // the WEP() is called the WEP() instead of WindowsExitProcedure(). // It takes up the minimum amount of space and is quickly located. // // Parameters: // bSystemExit - Type of exit // // Return Value: // TRUE. // //*************************************************************************** int CALLBACK WEP( int bSystemExit ) { return TRUE; } //*************************************************************************** // // FMExtensionProc() // // Purpose: // This is an application-defined callback function. It processes menu // commands and messages sent to FM_UTILS.DLL. // // Parameters: // hWndExtension - Identifies the File Manager window // wMessage - Message sent to extension DLL // lParam - Message information // // Return Value: // When the wMessage is FMEVENT_LOAD, the handle to extension's menu // should be returned; otherwise a NULL value. // //*************************************************************************** HMENU CALLBACK FMExtensionProc( HWND hWndExtension, WORD wMessage, LONG lParam ) { LPFMS_LOAD lpload; FARPROC lpDialogProc; switch( wMessage ) { case FMEVENT_LOAD: lpload = (LPFMS_LOAD) lParam; // Assign the menu handle from the DLL's resource ghMenu = LoadMenu( ghDllInst, "UtilsExtensionMenu" ); lpload->hMenu = ghMenu; // This is the delta we are being assigned. gwMenuDelta = lpload->wMenuDelta; lpload->dwSize = sizeof( FMS_LOAD ); // Assign the popup menu name for this extension NameMenu( lpload->szMenuName ); InitializeMenu( ghMenu ); PreLoadPrograms(); return ghMenu; // Kill any utilities we started, and free up allocated memory case FMEVENT_UNLOAD: KillPreLoadedPrograms(); CleanUpMenu(); break; case IDM_ABOUT: lpDialogProc = (FARPROC)AboutDlgProc; DialogBoxParam( ghDllInst, "About", hWndExtension, lpDialogProc, (LONG)hWndExtension ); break; default: // If one of our menu items chosen, process it, otherwise ignore if( wMessage >= IDM_USERPROG && wMessage <= IDM_MAXUSERPROG ) UserProgram( wMessage-IDM_USERPROG, hWndExtension ); break; } return NULL; } //*************************************************************************** // // NameMenu() // // Purpose: // We parse WINFILE.INI, looking for an entry 'UserMenuName='. If found // we use *that* name for our added menu, otherwise we use the default // "&Utilities" // // Parameters: // szName - the buffer to put the Menu Name into. // // Return Value: // none. // // Notes: // We don't perform *any* validity checking on the string, so it is quite // possible for the user to specify "&File" (for example) and totally // confuse him/herself (but not Windows, fortunately). // //*************************************************************************** void NameMenu( LPSTR szName ) { GetPrivateProfileString( (LPSTR)"FM_Utils Extension", (LPSTR)"UserMenuName", (LPSTR)"&Utilities", szName, MENU_TEXT_LEN, (LPSTR)"WINFILE.INI" ); } //*************************************************************************** // // InitializeMenu() // // Purpose: // We parse WINFILE.INI, looking for the list of User Programs and // descriptions to add to our menu. To save time, we dynamically // allocate buffers to store the program names & descriptions. // As we find each complete entry, we add it to the menu. // // Parameters: // none. // // Return Value: // none. // // Notes: // We really don't *need* to store the Description, because it's of // little use to us once it's added to the menu. However, we may // someday find use for it, so we may as well keep it. // //*************************************************************************** int InitializeMenu( HMENU hMenu ) { WORD wProgNum; int iNumItems = 0; char szUserProgEntry[ ENTRY_TAG_LENGTH ]; for( wProgNum = 0; wProgNum < NUM_USERPROGS; ++wProgNum ) { UserProg[wProgNum].fActive = FALSE; // Get the Utility's path wsprintf( szUserProgEntry, "UserProg%u", wProgNum+1 ); GetPrivateProfileString( (LPSTR)"FM_Utils Extension", (LPSTR)szUserProgEntry, "", szTempBuffer, PATH_NAME_LEN, (LPSTR)"WINFILE.INI" ); if( !( strlen( szTempBuffer ))) break; else { UserProg[wProgNum].szPath=(NPSTR)LocalAlloc( LPTR, strlen( szTempBuffer )+1 ); if( !UserProg[wProgNum].szPath ) { MessageBox( NULL, "Memory Allocation Error\nCreating Extension Menu", "File Manager", MB_ICONEXCLAMATION | MB_OK ); break; } strcpy( UserProg[wProgNum].szPath, szTempBuffer ); } // Get the Description to add to the menu wsprintf( szUserProgEntry, "UserDesc%u", wProgNum+1 ); GetPrivateProfileString( (LPSTR)"FM_Utils Extension", (LPSTR)szUserProgEntry, "", szTempBuffer, PROG_DESC_LENGTH, (LPSTR)"WINFILE.INI" ); if( !( strlen( szTempBuffer ))) { LocalFree( (HLOCAL)UserProg[wProgNum].szPath ); break; } else { UserProg[wProgNum].szDescription=(NPSTR)LocalAlloc( LPTR, strlen( szTempBuffer )+1 ); if( !UserProg[wProgNum].szDescription ) { LocalFree( (HLOCAL)UserProg[wProgNum].szPath ); MessageBox( NULL, "Memory Allocation Error\nCreating Extension Menu", "File Manager", MB_ICONEXCLAMATION | MB_OK ); break; } strcpy( UserProg[wProgNum].szDescription, szTempBuffer ); } // If all is okay, prepare to add this item to the menu UserProg[wProgNum].fActive = TRUE; AppendMenu( hMenu, MF_STRING | MF_ENABLED, IDM_USERPROG+wProgNum, (LPSTR)UserProg[wProgNum].szDescription ); ++iNumItems; } return iNumItems; } //*************************************************************************** // // CleanUpMenu() // // Purpose: // To be nice folks, we go through and free up all of our allocated // memory. // // Parameters: // none. // // Return Value: // none. // //*************************************************************************** void CleanUpMenu() { WORD wProgNum; for( wProgNum = 0; wProgNum < NUM_USERPROGS; ++wProgNum ) { if( !UserProg[wProgNum].fActive ) break; LocalFree( (HLOCAL)UserProg[wProgNum].szPath ); LocalFree( (HLOCAL)UserProg[wProgNum].szDescription ); UserProg[wProgNum].fActive = FALSE; } } //*************************************************************************** // // PreLoadPrograms() // // Purpose: // Parse the name of a utility to run, and attempt to start it using // WinExec(). If we're successful, we determine and keep the window // handle of the main WndProc for that program. We'll need that later to // close the program. // // Parameters: // none. // // Return Value: // none. // //*************************************************************************** void PreLoadPrograms() { WORD wProgNum; char szPreLoadProgEntry[ ENTRY_TAG_LENGTH ]; for( wProgNum = 0; wProgNum < NUM_PRELOADPROGS; ++wProgNum ) { PreLoadProg[wProgNum].fActive = FALSE; // Get the Utility's path wsprintf( szPreLoadProgEntry, "PreLoad%u", wProgNum+1 ); GetPrivateProfileString( (LPSTR)"FM_Utils Extension", (LPSTR)szPreLoadProgEntry, "", szTempBuffer, PATH_NAME_LEN, (LPSTR)"WINFILE.INI" ); if( !( strlen( szTempBuffer ))) break; else { PreLoadProg[wProgNum].szPath=(NPSTR)LocalAlloc( LPTR, strlen( szTempBuffer )+1 ); if( !PreLoadProg[wProgNum].szPath ) { MessageBox( NULL, "Memory Allocation Error\nPre-Loading Programs", "File Manager", MB_ICONEXCLAMATION | MB_OK ); break; } strcpy( PreLoadProg[wProgNum].szPath, szTempBuffer ); } // If all is okay, attempt start the program PreLoadProg[wProgNum].hInst = WinExec( (LPSTR)PreLoadProg[wProgNum].szPath, SW_SHOWMINNOACTIVE ); // If we failed, don't bother to notify. Otherwise, determine the handle if( PreLoadProg[wProgNum].hInst >= 32 ) { PreLoadProg[wProgNum].fActive = TRUE; PreLoadProg[wProgNum].hWnd = DetermineProgramHandle( PreLoadProg[wProgNum].hInst ); } } } //*************************************************************************** // // DetermineProgramHandle() // // Purpose: // Given the Instance Handle of a program, we want to find out the Window // handle of its main WndProc. // // Parameters: // hInst - Instance handle of the app we're trying to find the handle of. // // Return Value: // hWnd - Window handle of app's main WndProc // //*************************************************************************** HWND DetermineProgramHandle( HINSTANCE hInst ) { HWND hWnd; HWND hWndParent; // Find the first top-level window that matches our selected instance handle. for( hWnd = GetWindow( GetDesktopWindow(), GW_CHILD ); hWnd; hWnd = GetWindow( hWnd, GW_HWNDNEXT ) ) { if( GetWindowWord( hWnd, GWW_HINSTANCE ) == hInst ) break; } // Iconized programs can have *two* top-level window handles, the icon // (or main window), and the icon title. // In case we didn't get the top-most window of this task, loop through // until GetParent returns NULL. Then, we have the window we want to send // our WM_CLOSE to. while( hWndParent = GetParent( hWnd ) ) hWnd = hWndParent; return hWnd; } //*************************************************************************** // // KillPreLoadedPrograms() // // Purpose: // We step through the window handles of the preloaded apps, and send // WM_CLOSE messages to them. Naturally, we check first to ensure that // the window handle is still valid. // // Parameters: // none. // // Return Value: // none. // // Notes: // We have no guarantee that, when the main WndProc of the application // receives a WM_CLOSE message, that it will actually terminate the // app. If somebody knows of a better way to handle this, I'd be *real* // happy to hear about it. // //*************************************************************************** void KillPreLoadedPrograms() { WORD wProgNum; for( wProgNum = 0; wProgNum < NUM_PRELOADPROGS; ++wProgNum ) { if( !PreLoadProg[wProgNum].fActive ) break; // Check to make sure the Window is still active, and still matches // the instance handle. If so, attempt to close it. if(( IsWindow( PreLoadProg[wProgNum].hWnd )) && ( GetWindowWord( PreLoadProg[wProgNum].hWnd, GWW_HINSTANCE ) == PreLoadProg[wProgNum].hInst )) { PostMessage( PreLoadProg[wProgNum].hWnd, WM_CLOSE, 0, 0L ); } LocalFree( (HLOCAL)PreLoadProg[wProgNum].szPath ); PreLoadProg[wProgNum].fActive = FALSE; } } //*************************************************************************** // // UserProgram() // // Purpose: // This is where we select which User Program has been selected to // run. If there are one or more files selected, we generate a command // line, and then execute it using WinExec(). // // Parameters: // wProgNum - Identifies the index of the user program to run // hWndExtension - Identifies the File Manager window // // Return Value: // none. // // Notes: // While it doesn't seem to be specifically documented anywhere, the // maximum number of characters in a command line can't exceed 128. // Since it's possible to select any number of files, it's pretty easy // to blow this limit. In order to maximize our use of this limited // space, we perform some trickery. // Since we know that File Manager (currently) doesn't allow selection // of multiple files across different directories or drives, we know // that the path portion of all selected files should be the same. So, // we determine the path portion, and actually make that directory our // current directory, and chop the path portion from all of the selected // files (WinExec() will search for the utility, if its full path is // not specified, using the standard Windows search mechanism). // If we still try to blow the length limit, a warning MessageBox will // pop up, and the command line will be truncated at the last possble // file addition. While actually *changing* the file selections in // File Manager, it looks like this isn't feasible. // //*************************************************************************** void UserProgram( WORD wProgNum, HWND hWndExtension ) { static FMS_GETFILESEL fmsFileInfo; NPSTR szCmdLine; WORD wSelFileCount; WORD wProgIndex; WORD wWinExecReturn; WORD wCmdLineLength; char *szFileName; if( !UserProg[wProgNum].fActive ) return; wSelFileCount = (WORD)SendMessage( hWndExtension, FM_GETSELCOUNTLFN, 0, 0L ); // if( !wSelFileCount ) // { // MessageBox( NULL, "No Files Selected!", "File Manager", // MB_ICONEXCLAMATION | MB_OK ); // return; // } szCmdLine = (NPSTR)LocalAlloc( LPTR, strlen( UserProg[wProgNum].szPath )+1 ); if( !szCmdLine ) { MessageBox( NULL, "Unable to create command line!", "File Manager", MB_ICONEXCLAMATION | MB_OK ); return; } strcpy( szCmdLine, UserProg[wProgNum].szPath ); for( wProgIndex = 0; wProgIndex < wSelFileCount; wProgIndex++ ) { SendMessage( hWndExtension, FM_GETFILESELLFN, wProgIndex, (LONG)(LPFMS_GETFILESEL)&fmsFileInfo ); // We know that all of the selected files will be in the same directory, // because File Manager doesn't allow selection across drives or // directories. We can use this to our advantage by determining the // selected directory, making it the current directory, and only putting // the actual file names on the command line. This maximizes our use of // the command line (which, remember, is limited to 128 characters). if( wProgIndex == 0 ) { strcpy( szTempBuffer, fmsFileInfo.szName ); szFileName = strrchr( szTempBuffer, '\\' ); // We don't chop the trailing '\' on root directory if( (WORD)( szFileName - szTempBuffer ) > 3 ) szFileName[0] = 0; else szFileName[1] = 0; _chdrive( szTempBuffer[0] - 'A' + 1 ); _chdir( szTempBuffer ); // OutputDebugString( (LPSTR)szTempBuffer ); // OutputDebugString( (LPSTR)"\n" ); } szFileName = (char *)( strrchr( fmsFileInfo.szName, '\\' ) + 1 ); wCmdLineLength = strlen( szCmdLine ) + strlen( szFileName ) + 2; if( wCmdLineLength <= MAX_CMDLINE_LENGTH ) { szCmdLine = (NPSTR)LocalReAlloc( (HLOCAL)szCmdLine, wCmdLineLength, LMEM_ZEROINIT ); if( !szCmdLine ) { LocalFree( (HLOCAL)szCmdLine ); MessageBox( NULL, "Unable to construct command line!", "File Manager", MB_ICONEXCLAMATION | MB_OK ); return; } strcat( szCmdLine, " " ); strcat( szCmdLine, szFileName ); } else { wsprintf( szTempBuffer, "Adding file path\n%s\nwould make command line too long.\nTruncating command line.", (LPSTR)fmsFileInfo.szName ); MessageBox( NULL, szTempBuffer, "File Manager", MB_ICONEXCLAMATION | MB_OK ); break; } } // OutputDebugString( (LPSTR)szCmdLine ); // OutputDebugString( (LPSTR)"\n" ); wWinExecReturn = WinExec( szCmdLine, SW_SHOWNORMAL ); LocalFree( (HLOCAL)szCmdLine ); if( wWinExecReturn < 32 ) { wsprintf( szTempBuffer, "Unable to Execute\n%s", UserProg[wProgNum].szPath ); MessageBox( NULL, szTempBuffer, "File Manager", MB_ICONEXCLAMATION | MB_OK ); } } //*************************************************************************** // // AboutDlgProc() // // Purpose: // Your typical "About" box, in case you forget who wrote this amazing // piece of code. :-) // // Parameters: // The usual DLGPROC parameters // // Return Value: // The typical DLGPROC BOOL return // //*************************************************************************** BOOL CALLBACK AboutDlgProc( HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam ) { switch( wMsg ) { case WM_COMMAND: switch( wParam ) { case IDOK: case IDCANCEL: EndDialog( hDlg, TRUE ); break; default: return FALSE; } return TRUE; default: break; } return FALSE; }