MINOR: Tweak About box and traymenu pos
[openwide.git] / openwide.c
blob92bdab8691869d17ada5a2c5438cb4fe785b03cb
1 /*
2 * Openwide -- control Windows common dialog
3 *
4 * Copyright (c) 2000 Luke Hudson
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 #include <windows.h>
24 /**
25 * @author Luke Hudson
26 * @licence GPL2
29 #include <objbase.h>
30 #include <shobjidl.h>
31 #include <shlguid.h>
32 #include <shlobj.h>
33 #include <shlwapi.h>
34 #include <shellapi.h>
35 #include <stdio.h>
36 #include "openwidedll.h"
37 #include "openwideres.h"
38 #include "owutil.h"
39 #include "owSharedUtil.h"
40 #include "openwide_proto.h"
43 // Command identifiers for the trayicon popup menu.
44 enum TrayCommands { IDM_ABOUT = 100, IDM_SETTINGS, IDM_QUIT };
46 // Window handles for the various windows
47 HWND ghwMain = NULL, ghwPropSheet = NULL;
49 // Global instance handle
50 HINSTANCE ghInstance = NULL;
52 // Icon handles; these icons are used in a few places
53 HICON ghIconLG = NULL, ghIconSm = NULL;
55 // This is a pointer shared data structure, containing all the useful info
56 POWSharedData gPowData = NULL;
58 // Handle to our shared memory area -- access to this is controlled by a mutex
59 HANDLE ghSharedMem = NULL;
61 // Mutex handle -- used to limit access to the shared memory area.
62 HANDLE ghMutex = NULL;
64 // Message id, from RegisterWindowMessage(...), which is used to notify closure of : TODO: find out!
65 UINT giCloseMessage = 0;
70 * Initialise the shared memory area
72 * initShareMem( mainWindowHandle );
74 * @returns 1 on success, 0 on failure
76 int initSharedMem(HWND hwnd)
78 int rVal = 0; // return value
80 // Create the mutex
81 ghMutex = CreateMutex(NULL, TRUE, OW_MUTEX_NAME);
82 if( !ghMutex )
83 return 0;
84 /* else if( GetLastError() == ERROR_ALREADY_EXISTS)
86 CloseHandle(ghMutex);
87 ghMutex = NULL;
88 return 0;
89 }*/
91 POWSharedData pRData = NULL; // Pointer to registry data
93 // try to read in saved settings from registry
94 HKEY hk = regCreateKey(HKEY_CURRENT_USER, OW_REGKEY_NAME);
95 if( hk )
97 BOOL bOK;
98 DWORD dwSize = regReadDWORD(hk, NULL, &bOK);
99 if( bOK && dwSize == sizeof(OWSharedData) )
100 pRData = (POWSharedData)regReadBinaryData(hk, "OWData");
101 regCloseKey(hk);
104 // Create a file mapping object against the system virtual memory.
105 // This will be used to share data between the application and instances of the dll.
106 ghSharedMem = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
107 sizeof(OWSharedData), OW_SHARED_FILE_MAPPING);
108 if(ghSharedMem)
110 // map a view of the 'file'
111 gPowData = (POWSharedData)MapViewOfFile(ghSharedMem, FILE_MAP_WRITE, 0,0,0);
112 if( gPowData )
114 // clear the shared mem
115 ZeroMemory(gPowData, sizeof(OWSharedData));
116 // If we succeeded in loading saved data from the registry,
117 // then copy it to the shared memory area
118 if( pRData )
119 CopyMemory(gPowData, pRData, sizeof(OWSharedData));
120 else
121 { // Nothing loaded from registry, so
122 // initialise the memory to some useful default values.
124 // Get screen dimensions
125 int w,h;
126 w = GetSystemMetrics(SM_CXSCREEN);
127 h = GetSystemMetrics(SM_CYSCREEN);
129 // Set the Open/Save dialog origin and width from the screen dimensions
130 gPowData->ptOrg.x = w/2 - w/4;
131 gPowData->ptOrg.y = (h - 2*h/3)/2;
132 gPowData->szDim.cx = w/2; // Screen width / 2
133 gPowData->szDim.cy = 2*h/3; // 2/3 of screen height
134 gPowData->iView = V_DETAILS; // Use details view
135 gPowData->iFocus = F_DIRLIST; // Focus directory listing
136 gPowData->bShowIcon = 1; // Show the tray icon
138 gPowData->hwListener = hwnd; // Listener window for trayicon events, etc.
139 gPowData->bDisable = 0; // Enable the hook!
140 gPowData->refCount = 0; // reference counting TODO: add explanation
141 giCloseMessage = gPowData->iCloseMsg
142 = RegisterWindowMessage("Lingo.OpenWide.ProcessFinished.Message");
143 rVal = 1; // Return value to indicate success
144 } // if( gPowData )
145 } // if( ghSharedMem )
147 if( pRData ) // free registry data
148 free(pRData);
150 releaseMutex(); // unlock Mutex, so shared memory area can be accessed.
151 return rVal;
152 } // END of initSharedMem(...)
157 * Free the shared memory area, once it is no longer in use.
159 void releaseSharedMem(void)
161 if( ghMutex ) // Obtain a lock on the shared memory access mutex
163 DWORD dwRes = WaitForSingleObject(ghMutex, INFINITE);
164 if(dwRes != WAIT_OBJECT_0)
166 // dbg("DLL: Failed to get ownership of mutex in releaseSharedMem!!!");
167 return;
171 // dbg("DLL: Releasing file mapping");
172 if( ghSharedMem )
174 if( gPowData )
176 UnmapViewOfFile(gPowData);
177 gPowData = NULL;
179 CloseHandle(ghSharedMem);
180 ghSharedMem = NULL;
182 ReleaseMutex(ghMutex);
183 } // END of releaseSharedMem()
186 int doApply(HWND hwnd)
188 DWORD dwRes;
189 if( ghMutex )
191 dwRes = WaitForSingleObject(ghMutex, INFINITE);
192 switch(dwRes)
194 case WAIT_OBJECT_0:
195 // okay, continue
196 break;
197 case WAIT_ABANDONED:
198 default:
199 return 0;
202 BOOL bOK;
203 int i = GetDlgItemInt(hwnd, IDE_LEFT, &bOK, TRUE);
204 if( bOK )
205 gPowData->ptOrg.x = i;
206 i = GetDlgItemInt(hwnd, IDE_TOP, &bOK, TRUE);
207 if( bOK )
208 gPowData->ptOrg.y = i;
209 i = GetDlgItemInt(hwnd, IDE_WIDTH, &bOK, TRUE);
210 if( bOK )
211 gPowData->szDim.cx = i;
212 i = GetDlgItemInt(hwnd, IDE_HEIGHT, &bOK, TRUE);
213 if( bOK )
214 gPowData->szDim.cy = i;
215 int idx = SendDlgItemMessage(hwnd, IDCB_FOCUS, CB_GETCURSEL, 0, 0);
216 if( idx != CB_ERR )
218 int f = SendDlgItemMessage(hwnd, IDCB_FOCUS, CB_GETITEMDATA, idx, 0);
219 if( f >= 0 && f < F_MAX )
220 gPowData->iFocus = f;
222 idx = SendDlgItemMessage(hwnd, IDCB_VIEW, CB_GETCURSEL, 0, 0);
223 if( idx != CB_ERR )
225 int v = SendDlgItemMessage(hwnd, IDCB_VIEW, CB_GETITEMDATA, idx, 0);
226 if( v >= 0 && v < V_MAX )
227 gPowData->iView = v;
229 gPowData->bStartMin = IsDlgButtonChecked(hwnd, IDC_STARTMIN) == BST_CHECKED;
230 BOOL bWinStart = IsDlgButtonChecked(hwnd, IDC_WSTARTUP) == BST_CHECKED;
232 HKEY hk = regCreateKey(HKEY_CURRENT_USER, OW_REGKEY_NAME);
233 if( hk )
235 regWriteDWORD(hk, NULL, sizeof(OWSharedData));
236 regWriteBinaryData(hk, "OWData", (byte *)gPowData, sizeof(OWSharedData));
237 regCloseKey(hk);
240 if( ghMutex )
241 ReleaseMutex(ghMutex);
243 setStartupApp(hwnd, bWinStart);
244 return 1;
249 * Determines if this application has a shortcut in the 'Start Menu->Startup' directory.
251 BOOL isStartupApp(HWND hwnd)
253 static char szBuf[MAX_PATH+1]; // path buffer
255 // Retrieve path to windows Startup folder
256 HRESULT hRes = SHGetFolderPath(hwnd, CSIDL_STARTUP, NULL, SHGFP_TYPE_CURRENT, szBuf);
257 if( hRes == S_OK )
259 PathAppend(szBuf, "OpenWide.lnk"); // Append the name of the link :: TODO - this should be cleverer, perhaps.
260 return fileExists(szBuf); // Check the shortcut exists :: TODO: Check if it points to the right place, too?
262 return FALSE;
266 * Makes this program start with Windows, by creating a shortcut in the Windows Startup folder
268 * setStartupApp( parentWindowOfErrors, isStartupApp )
270 * @param isStartupApp -- set to 1 to create the shortcut, 0 to remove it.
271 * @returns 1 on success, 0 on failure
273 int setStartupApp(HWND hwnd, BOOL bSet)
275 char szBuf[MAX_PATH+1], szModule[MAX_PATH+1]; // some path buffers
277 // Empty the string buffers
278 ZeroMemory(szBuf, sizeof(szBuf)/sizeof(char));
279 ZeroMemory(szModule, sizeof(szBuf)/sizeof(char));
281 // Retrieve the path to this executable
282 if( !GetModuleFileName(NULL, szModule, MAX_PATH) )
283 return 0;
285 // Get the windows startup folder
286 HRESULT hRes = SHGetFolderPath(hwnd, CSIDL_STARTUP, NULL, SHGFP_TYPE_CURRENT, szBuf);
287 if( hRes != S_OK || !PathAppend(szBuf, "OpenWide.lnk")) // Append link path to startup folder path as side-effect
288 return 0;
289 if( bSet ) // Create the shortcut?
291 if( fileExists(szBuf) )
292 hRes = delFile(hwnd, szBuf); // Delete existing if found
293 hRes = CreateLink(szModule, szBuf, "OpenWide - control Open&Save dialogs..."); // Create new shortcut
294 return hRes == S_OK;
296 else // Delete the shortcut
298 return delFile(hwnd, szBuf);
300 } // END of setStartupApp(...)
304 * Create an event-listener window, which will be used for communication between the
305 * app and DLL instances ??? TODO:: Check this!!
307 * NOTE: Looks like this is badly named, doesn't really do SFA with the listener win!!! @see createListenerWindow()
308 * @param hwnd -- parent window?
310 int initListener(HWND hwnd)
312 if(!initSharedMem(hwnd))
313 return 0; // Make sure the shared memory area is set up.
315 BOOL bMinimize = FALSE, bIcon = TRUE;
317 // wait for then lock shared data
318 if( !waitForMutex() )
319 return 0;
321 bMinimize = gPowData->bStartMin; // get the preferences from the shared memory
322 bIcon = gPowData->bShowIcon;
324 /// released shared data
325 releaseMutex();
327 if(bIcon)
328 addTrayIcon(hwnd);
330 // Show property sheet only if the preferences specify it.
331 if( bMinimize )
332 ghwPropSheet = NULL;
333 else
334 ghwPropSheet = showDlg(hwnd);
336 // Create the main event hook
337 if( !setHook() )
339 Warn( "Failed to set hook: %s", geterrmsg() );
340 return 0;
342 return 1;
343 } // END of initListener(...)
347 * Create, and show the popup menu for the trayicon
349 * @param hwnd -- window to receive WM_COMMAND messages.
351 void doTrayMenu(HWND hwnd)
353 if( ghwPropSheet ) // Show the property sheet window
354 SetForegroundWindow(ghwPropSheet);
356 // Create the popup menu structure
357 HMENU hm = CreatePopupMenu();
358 AppendMenu(hm, MF_STRING, IDM_ABOUT, "&About OpenWide...");
359 AppendMenu(hm, MF_GRAYED | MF_SEPARATOR, 0, NULL);
360 AppendMenu(hm, MF_STRING | (ghwPropSheet ? MF_GRAYED : 0), IDM_SETTINGS, "&Settings...");
361 AppendMenu(hm, MF_STRING | (ghwPropSheet ? MF_GRAYED : 0), IDM_QUIT, "&Quit");
363 SetMenuDefaultItem(hm, IDM_SETTINGS, FALSE);
365 POINT pt;
366 GetCursorPos(&pt); // Find mouse location
368 // Popup the menu
369 TrackPopupMenu(hm, TPM_RIGHTBUTTON | TPM_BOTTOMALIGN | TPM_RIGHTALIGN,
370 pt.x+8, pt.y+8, 0, hwnd, NULL);
372 // Delete the menu structure again.
373 DestroyMenu(hm);
374 } // END of doTrayMenu(...)
377 * Display the main property sheet dialog
379 * @param hwnd -- parent window
381 void showSettingsDlg(HWND hwnd)
383 if( ghwPropSheet ) // un-hide it if it exists already.
385 ShowWindow(ghwPropSheet, SW_SHOWNORMAL);
386 SetForegroundWindow(ghwPropSheet);
388 else // create and show the dialog if it doesn't already exist
389 ghwPropSheet = showDlg(hwnd);
390 } // END of showSettingsDlg(...)
394 * Quit the application, running cleanup tasks first.
396 * This is a high-level function which makes up part of the 'public' API of the program,
397 * at least, as far as it has such an clear designed concept :(
399 * TODO: Make sure the above statement is actually correct, and sort the functions into these sorts of categories somewhere.
401 void doQuit(void) {
402 if( ghwPropSheet ) // don't quit with the settings dialog open
404 SetForegroundWindow(ghwPropSheet);
405 Warn("Please close the settings dialog box before trying to quit");
406 return;
408 //rmvHook();
409 if( waitForMutex() )
411 gPowData->bDisable = TRUE; // Tell hook not to subclass any more windows
413 if(gPowData->refCount == 0) // if there are no more subclassed windows
415 PostQuitMessage(0); // quit program
417 releaseMutex();
419 remTrayIcon(ghwMain);
424 * Listener window callback
426 LRESULT WINAPI CALLBACK wpListener(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
428 static UINT uTaskbarMsg=0;
429 switch(msg)
431 case WM_CREATE:
432 if(!initListener(hwnd))
434 Warn("OpenWide failed to initialize properly. Please report this bug:\r\nErr msg: %s", geterrmsg());
435 return -1;
437 uTaskbarMsg = RegisterWindowMessage("TaskbarCreated"); // TODO:: WHAT IS THIS?
438 break;
440 // Handle trayicon menu commands
441 case WM_COMMAND:
442 switch(LOWORD(wp))
444 case IDM_ABOUT:
445 MessageBox(hwnd, "OpenWide is written by Luke Hudson. (c)2005-2011\r\nSee http://speak.geek.nz", "About OpenWide", MB_OK);
446 break;
447 case IDM_SETTINGS:
448 showSettingsDlg(hwnd);
449 break;
450 case IDM_QUIT:
451 doQuit();
452 break;
454 break;
456 // Handle trayicon interaction events
457 case WM_TRAYICON:
458 switch(lp)
460 case WM_RBUTTONDOWN:
461 doTrayMenu(hwnd);
462 break;
463 case WM_LBUTTONDBLCLK:
464 showSettingsDlg(hwnd);
465 break;
466 case WM_LBUTTONUP:
467 /* ShowWindow(hwnd, SW_SHOWNORMAL);
468 EnableWindow(hwnd, TRUE);
469 remTrayIcon(hwnd);
470 SetFocus(hwnd);*/
471 break;
473 break;
474 case WM_DESTROY:
475 // dbg("OWApp: listener destroying");
476 rmvHook();
477 releaseSharedMem();
478 break;
479 default:
480 if( msg == giCloseMessage )
482 // dbg("OWApp: Received dll close msg");
483 if( waitForMutex() )
485 // dbg("OWApp: refCount is %d", gPowData->refCount);
486 if( gPowData->refCount == 0 && gPowData->bDisable)
487 PostQuitMessage(0);
488 releaseMutex();
491 else if( msg == uTaskbarMsg ) // taskbar re-created, re-add icon if option is enabled.
493 if( !waitForMutex() )
494 return 0;
495 BOOL bIcon = gPowData->bShowIcon;
496 releaseMutex();
498 if(bIcon) addTrayIcon(hwnd);
500 return DefWindowProc(hwnd, msg, wp, lp);
502 return 0;
507 * Callback for the placement window. This is the proxy window which can be
508 * resized and moved to set the desired Open/Save dialog dimensions.
510 BOOL WINAPI CALLBACK wpPlacement(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
512 static LPRECT pr = NULL;
513 static BOOL bXP; // Are we using WinXP ? -- Changes the minimum dialog dimensions.
514 switch(msg)
516 case WM_INITDIALOG:
517 pr = (LPRECT)lp;
518 if(!pr)
519 EndDialog(hwnd, 0);
520 bXP = isWinXP();
521 MoveWindow(hwnd, pr->left, pr->top, pr->right, pr->bottom, FALSE);
522 break;
524 case WM_COMMAND:
525 switch(LOWORD(wp))
527 case IDOK:
528 GetWindowRect(hwnd, pr);
529 EndDialog(hwnd, 1);
530 break;
531 case IDCANCEL:
532 EndDialog(hwnd, 0);
533 break;
535 break;
537 // Used to limit the minimum dimensions while resizing
538 case WM_GETMINMAXINFO:
540 MINMAXINFO * pmm = (MINMAXINFO *)lp;
541 if(!pmm)
542 break;
543 if( bXP )
545 pmm->ptMinTrackSize.x = OW_XP_MINWIDTH;
546 pmm->ptMinTrackSize.y = OW_XP_MINHEIGHT;
548 else
550 pmm->ptMinTrackSize.x = OW_2K_MINWIDTH;
551 pmm->ptMinTrackSize.y = OW_2K_MINHEIGHT;
554 break;
556 case WM_CLOSE:
557 EndDialog(hwnd, 0);
558 break;
559 default:
560 return 0;
562 return 1;
566 /* Create our trayicon */
567 int addTrayIcon(HWND hwnd)
569 return Add_TrayIcon( ghIconSm, "OpenWide\r\nRight-Click for menu...", hwnd, WM_TRAYICON, 0);
572 /* Remove the trayicon! */
573 void remTrayIcon(HWND hwnd)
575 Rem_TrayIcon( hwnd, WM_TRAYICON, 0 );
579 /* Creates the listener window */
580 HWND createListenerWindow(void)
582 static BOOL bRegd = FALSE;
583 if(!bRegd)
585 WNDCLASSEX wc;
586 ZeroMemory(&wc, sizeof(wc));
587 wc.cbSize = sizeof(wc);
588 wc.hInstance = ghInstance;
589 wc.lpfnWndProc = wpListener;
590 wc.lpszClassName = "Lingo.OpenWide.Listener";
591 wc.hIcon = ghIconLG;
592 wc.hIconSm = ghIconSm;
593 bRegd = (RegisterClassEx(&wc) != 0);
595 return CreateWindowEx( WS_EX_NOACTIVATE, "Lingo.OpenWide.Listener", "Lingo.OpenWide",
596 WS_POPUP, 0,0, 1,1, NULL, NULL, ghInstance, NULL);
599 // TODO: Remove this useless function!
600 int createWin(void)
602 ghwMain = createListenerWindow();
603 return 1;
606 // TODO: why the new naming convention ?
607 int ow_init(void)
609 HRESULT hRes = CoInitialize(NULL); // Initialise COM -- for some reason I forget, we need to do this.
610 if( hRes != S_OK && hRes != S_FALSE )
611 return 0;
612 // Load the icons from the .exe resource section
613 ghIconSm = (HICON)LoadImage(ghInstance, MAKEINTRESOURCE(IDI_TRAY), IMAGE_ICON, 16,16, LR_SHARED | LR_VGACOLOR);
614 ghIconLG = (HICON)LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_TRAY));
615 if( !createWin() )
616 return 0;
617 return 1;
621 void ow_shutdown(void)
623 if(ghwMain)
625 DestroyWindow(ghwMain);
626 ghwMain = NULL;
628 CoUninitialize();
632 int WINAPI WinMain(HINSTANCE hi, HINSTANCE hiPrv, LPSTR fakeCmdLine, int iShow)
634 // This Mutex is used to ensure that only one instance of this app can be running at a time.
635 HANDLE hMutex = NULL;
636 hMutex = CreateMutex(NULL, FALSE, "OpenWide_App_Mutex");
637 if( hMutex && GetLastError()==ERROR_ALREADY_EXISTS )
639 // This app is already loaded, so find its window and send it a message.
640 CloseHandle(hMutex);
641 HWND hw = FindWindowEx(NULL, NULL, "Lingo.OpenWide.Listener", "Lingo.OpenWide");
642 if(hw) // Fake a double click on the trayicon -- to show the window.
643 SendMessage(hw, WM_TRAYICON, 0, WM_LBUTTONDBLCLK);
644 return 0; // Exit this instance of the app.
647 ghInstance = hi;
648 if( !ow_init() )
650 ow_shutdown();
651 return 0;
654 MSG msg;
655 int ret;
657 while( (ret = GetMessage(&msg, NULL, 0,0) ) != 0 )
659 if( ret == -1 )
660 break;
661 if( !PropSheet_IsDialogMessage(ghwPropSheet, &msg) ) //IsDialogMessage(ghwMain, &msg) )
663 TranslateMessage(&msg);
664 DispatchMessage(&msg);
666 if( !PropSheet_GetCurrentPageHwnd(ghwPropSheet) )
668 DestroyWindow(ghwPropSheet);
669 ghwPropSheet=(void *)NULL;
672 // dbg("OWApp: Shutting down");
673 ow_shutdown();
674 return 0;