Hardcode "wineshelllink" in winemenubuilder, remove it from the config
[wine] / programs / winecfg / drive.c
1 /*
2  * Drive management UI code
3  *
4  * Copyright 2003 Mark Westcott
5  * Copyright 2003 Mike Hearn
6  * Copyright 2004 Chris Morgan
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 /* TODO: */
25 /* - Support for devices(not sure what this means) */
26 /* - Various autodetections */
27
28 #include <assert.h>
29 #include <stdarg.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <string.h>
33
34 #include <windef.h>
35 #include <winbase.h>
36 #include <winreg.h>
37 #include <wine/debug.h>
38 #include <shellapi.h>
39 #include <objbase.h>
40 #include <shlguid.h>
41 #include <shlwapi.h>
42 #include <shlobj.h>
43
44 #include "winecfg.h"
45 #include "resource.h"
46
47
48 WINE_DEFAULT_DEBUG_CHANNEL(winecfg);
49
50 typedef struct drive_entry_s
51 {
52     char letter;
53     char *unixpath;
54     char *label;
55     char *serial;
56     uint type;
57
58     BOOL in_use;
59 } drive_entry_t;
60
61 static BOOL updatingUI = FALSE;
62 static drive_entry_t* editDriveEntry;
63 static int lastSel = 0; /* the last drive selected in the property sheet */
64 drive_entry_t drives[26]; /* one for each drive letter */
65
66 int getDrive(char letter)
67 {
68     return (toupper(letter) - 'A');
69 }
70
71 BOOL addDrive(char letter, char *targetpath, char *label, char *serial, uint type)
72 {
73     int driveIndex = getDrive(letter);
74
75     if(drives[driveIndex].in_use)
76         return FALSE;
77
78     WINE_TRACE("letter == '%c', unixpath == '%s', label == '%s', serial == '%s', type == %d\n",
79                letter, targetpath, label, serial, type);
80
81     drives[driveIndex].letter   = toupper(letter);
82     drives[driveIndex].unixpath = strdup(targetpath);
83     drives[driveIndex].label    = strdup(label);
84     drives[driveIndex].serial   = strdup(serial);
85     drives[driveIndex].type     = type;
86     drives[driveIndex].in_use   = TRUE;
87
88     return TRUE;
89 }
90
91 /* frees up the memory associated with a drive and returns the */
92 /* pNext of the given drive entry */
93 void freeDrive(drive_entry_t *pDrive)
94 {
95     free(pDrive->unixpath);
96     free(pDrive->label);
97     free(pDrive->serial);
98
99     pDrive->in_use = FALSE;
100 }
101
102 void setDriveLabel(drive_entry_t *pDrive, char *label)
103 {
104     WINE_TRACE("pDrive->letter '%c', label = '%s'\n", pDrive->letter, label);
105     free(pDrive->label);
106     pDrive->label = strdup(label);
107 }
108
109 void setDriveSerial(drive_entry_t *pDrive, char *serial)
110 {
111     WINE_TRACE("pDrive->letter '%c', serial = '%s'\n", pDrive->letter, serial);
112     free(pDrive->serial);
113     pDrive->serial = strdup(serial);
114 }
115
116 void setDrivePath(drive_entry_t *pDrive, char *path)
117 {
118     WINE_TRACE("pDrive->letter '%c', path = '%s'\n", pDrive->letter, path);
119     free(pDrive->unixpath);
120     pDrive->unixpath = strdup(path);
121 }
122
123 BOOL copyDrive(drive_entry_t *pSrc, drive_entry_t *pDst)
124 {
125     if(pDst->in_use)
126     {
127         WINE_TRACE("pDst already in use\n");
128         return FALSE;
129     }
130
131     if(!pSrc->unixpath) WINE_TRACE("!pSrc->unixpath\n");
132     if(!pSrc->label) WINE_TRACE("!pSrc->label\n");
133     if(!pSrc->serial) WINE_TRACE("!pSrc->serial\n");
134
135     pDst->unixpath = strdup(pSrc->unixpath);
136     pDst->label = strdup(pSrc->label);
137     pDst->serial = strdup(pSrc->serial);
138     pDst->type = pSrc->type;
139     pDst->in_use = TRUE;
140
141     return TRUE;
142 }
143
144 BOOL moveDrive(drive_entry_t *pSrc, drive_entry_t *pDst)
145 {
146     WINE_TRACE("pSrc->letter == %c, pDst->letter == %c\n", pSrc->letter, pDst->letter);
147
148     if(!copyDrive(pSrc, pDst))
149     {
150         WINE_TRACE("copyDrive failed\n");
151         return FALSE;
152     }
153
154     freeDrive(pSrc);
155     return TRUE;
156 }
157
158 int refreshDriveDlg (HWND dialog)
159 {
160   int driveCount = 0;
161   int doesDriveCExist = FALSE;
162   int i;
163
164   WINE_TRACE("\n");
165
166   updatingUI = TRUE;
167
168   /* Clear the listbox */
169   SendMessageA(GetDlgItem(dialog, IDC_LIST_DRIVES), LB_RESETCONTENT, 0, 0);
170
171   for(i = 0; i < 26; i++)
172   {
173       char *title = 0;
174       int titleLen;
175       int itemIndex;
176
177       /* skip over any unused drives */
178       if(!drives[i].in_use)
179           continue;
180
181       if(drives[i].letter == 'C')
182           doesDriveCExist = TRUE;
183
184       titleLen = snprintf(title, 0, "Drive %c:\\ %s", 'A' + i,
185                           drives[i].unixpath);
186       titleLen++; /* add a byte for the trailing null */
187
188       title = malloc(titleLen);
189
190       /* the %s in the item label will be replaced by the drive letter, so -1, then
191          -2 for the second %s which will be expanded to the label, finally + 1 for terminating #0 */
192       snprintf(title, titleLen, "Drive %c:\\ %s", 'A' + i,
193                drives[i].unixpath);
194
195       WINE_TRACE("title is '%s'\n", title);
196
197       /* the first SendMessage call adds the string and returns the index, the second associates that index with it */
198       itemIndex = SendMessageA(GetDlgItem(dialog, IDC_LIST_DRIVES), LB_ADDSTRING ,(WPARAM) -1, (LPARAM) title);
199       SendMessageA(GetDlgItem(dialog, IDC_LIST_DRIVES), LB_SETITEMDATA, itemIndex, (LPARAM) &drives[i]);
200
201       free(title);
202       driveCount++;
203   }
204
205   WINE_TRACE("loaded %d drives\n", driveCount);
206   SendDlgItemMessage(dialog, IDC_LIST_DRIVES, LB_SETSEL, TRUE, lastSel);
207
208   /* show the warning if there is no Drive C */
209   if (!doesDriveCExist)
210     ShowWindow(GetDlgItem(dialog, IDS_DRIVE_NO_C), SW_NORMAL);
211   else
212     ShowWindow(GetDlgItem(dialog, IDS_DRIVE_NO_C), SW_HIDE);
213   
214   /* disable or enable controls depending on whether we are editing global vs app specific config */
215   if (appSettings == EDITING_GLOBAL) {
216     WINE_TRACE("enabling controls\n");
217     enable(IDC_LIST_DRIVES);
218     enable(IDC_BUTTON_ADD);
219     enable(IDC_BUTTON_REMOVE);
220     enable(IDC_BUTTON_EDIT);
221     enable(IDC_BUTTON_AUTODETECT);
222     
223   } else {
224     WINE_TRACE("disabling controls\n");
225     disable(IDC_LIST_DRIVES);
226     disable(IDC_BUTTON_ADD);
227     disable(IDC_BUTTON_REMOVE);
228     disable(IDC_BUTTON_EDIT);
229     disable(IDC_BUTTON_AUTODETECT);    
230   }
231
232   
233   updatingUI = FALSE;
234   return driveCount;
235 }
236
237 /******************************************************************************/
238 /*  The Drive Editing Dialog                                                  */
239 /******************************************************************************/
240 #define DRIVE_MASK_BIT(B) 1<<(toupper(B)-'A')
241
242 typedef struct {
243   const uint sCode;
244   const char *sDesc;
245 } code_desc_pair;
246
247 static code_desc_pair type_pairs[] = {
248   {DRIVE_FIXED,      "Local hard disk"},
249   {DRIVE_REMOTE,     "Network share" },
250   {DRIVE_REMOVABLE,  "Floppy disk"},
251   {DRIVE_CDROM,      "CD-ROM"}
252 };
253 #define DRIVE_TYPE_DEFAULT 1
254
255
256 void fill_drive_droplist(long mask, char currentLetter, HWND hDlg)
257 {
258   int i;
259   int selection;
260   int count;
261   int next_letter;
262   char sName[4] = "A:";
263
264   for( i=0, count=0, selection=-1, next_letter=-1; i <= 'Z'-'A'; ++i ) {
265     if( mask & DRIVE_MASK_BIT('A'+i) ) {
266       int index;
267       
268       sName[0] = 'A' + i;
269       index = SendDlgItemMessage( hDlg, IDC_COMBO_LETTER, CB_ADDSTRING, 0, (LPARAM) sName );
270                           
271       if( toupper(currentLetter) == 'A' + i ) {
272           selection = count;
273       }
274       
275       if( i >= 2 && next_letter == -1){ /*default drive is first one of C-Z */
276           next_letter = count;
277       }
278       
279       count++;
280     }
281   }
282   
283   if( selection == -1 ) {
284     selection = next_letter;
285   }
286   
287   SendDlgItemMessage( hDlg, IDC_COMBO_LETTER, CB_SETCURSEL, selection, 0 );
288 }
289
290 #define BOX_MODE_CD_ASSIGN 1
291 #define BOX_MODE_CD_AUTODETECT 2
292 #define BOX_MODE_NONE 3
293 #define BOX_MODE_NORMAL 4
294 void enable_labelserial_box(HWND dialog, int mode)
295 {
296   WINE_TRACE("mode=%d\n", mode);
297   switch (mode) {
298       case BOX_MODE_CD_ASSIGN:
299 /*      enable(IDC_RADIO_AUTODETECT); */
300         enable(IDC_RADIO_ASSIGN);
301         disable(IDC_EDIT_DEVICE);
302         disable(IDC_BUTTON_BROWSE_DEVICE);
303         enable(IDC_EDIT_SERIAL);
304         enable(IDC_EDIT_LABEL);
305         enable(IDC_STATIC_SERIAL);
306         enable(IDC_STATIC_LABEL);
307         break;
308         
309       case BOX_MODE_CD_AUTODETECT:
310 /*      enable(IDC_RADIO_AUTODETECT); */
311         enable(IDC_RADIO_ASSIGN);
312         enable(IDC_EDIT_DEVICE);
313         enable(IDC_BUTTON_BROWSE_DEVICE);
314         disable(IDC_EDIT_SERIAL);
315         disable(IDC_EDIT_LABEL);
316         disable(IDC_STATIC_SERIAL);
317         disable(IDC_STATIC_LABEL);
318         break;
319
320       case BOX_MODE_NONE:
321         disable(IDC_RADIO_AUTODETECT);
322         disable(IDC_RADIO_ASSIGN);
323         disable(IDC_EDIT_DEVICE);
324         disable(IDC_BUTTON_BROWSE_DEVICE);
325         disable(IDC_EDIT_SERIAL);
326         disable(IDC_EDIT_LABEL);
327         disable(IDC_STATIC_SERIAL);
328         disable(IDC_STATIC_LABEL);
329         break;
330
331       case BOX_MODE_NORMAL:
332         disable(IDC_RADIO_AUTODETECT);
333         enable(IDC_RADIO_ASSIGN);
334         disable(IDC_EDIT_DEVICE);
335         disable(IDC_BUTTON_BROWSE_DEVICE);
336         enable(IDC_EDIT_SERIAL);
337         enable(IDC_EDIT_LABEL);
338         enable(IDC_STATIC_SERIAL);
339         enable(IDC_STATIC_LABEL);
340         break;  
341   }
342 }
343
344 /* This function produces a mask for each drive letter that isn't currently used. Each bit of the long result
345  * represents a letter, with A being the least significant bit, and Z being the most significant.
346  *
347  * To calculate this, we loop over each letter, and see if we can get a drive entry for it. If so, we
348  * set the appropriate bit. At the end, we flip each bit, to give the desired result.
349  *
350  * The letter parameter is always marked as being available. This is so the edit dialog can display the
351  * currently used drive letter alongside the available ones.
352  */
353 long drive_available_mask(char letter)
354 {
355   long result = 0;
356   int i;
357
358   WINE_TRACE("\n");
359
360   
361   for(i = 0; i < 26; i++)
362   {
363       if(!drives[i].in_use) continue;
364       result |= (1 << (toupper(drives[i].letter) - 'A'));
365   }
366
367   result = ~result;
368   if (letter) result |= DRIVE_MASK_BIT(letter);
369   
370   WINE_TRACE( "finished drive letter loop with %lx\n", result );
371   return result;
372 }
373
374 void advancedDriveEditDialog(HWND hDlg, BOOL showAdvanced)
375 {
376 #define ADVANCED_DELTA      120
377
378     static RECT okpos;
379     static BOOL got_initial_ok_position = FALSE;
380
381     static RECT windowpos; /* we only use the height of this rectangle */
382     static BOOL got_initial_window_position = FALSE;
383
384     static RECT current_window;
385
386     INT state;
387     INT offset;
388     char *text;
389
390     if(!got_initial_ok_position)
391     {
392         POINT pt;
393         GetWindowRect(GetDlgItem(hDlg, ID_BUTTON_OK), &okpos);
394         pt.x = okpos.left;
395         pt.y = okpos.top;
396         ScreenToClient(hDlg, &pt);
397         okpos.right+= (pt.x - okpos.left);
398         okpos.bottom+= (pt.y - okpos.top);
399         okpos.left = pt.x;
400         okpos.top = pt.y;
401         got_initial_ok_position = TRUE;
402     }
403
404     if(!got_initial_window_position)
405     {
406         GetWindowRect(hDlg, &windowpos);
407         got_initial_window_position = TRUE;
408     }
409           
410     if(showAdvanced)
411     {
412         state = SW_NORMAL;
413         offset = 0;
414         text = "Hide Advanced";
415     } else
416     {
417         state = SW_HIDE;
418         offset = ADVANCED_DELTA;
419         text = "Show Advanced";
420     }
421
422     ShowWindow(GetDlgItem(hDlg, IDC_STATIC_TYPE), state);
423     ShowWindow(GetDlgItem(hDlg, IDC_COMBO_TYPE), state);
424     ShowWindow(GetDlgItem(hDlg, IDC_BOX_LABELSERIAL), state);
425     ShowWindow(GetDlgItem(hDlg, IDC_RADIO_AUTODETECT), state);
426     ShowWindow(GetDlgItem(hDlg, IDC_RADIO_ASSIGN), state);
427     ShowWindow(GetDlgItem(hDlg, IDC_EDIT_LABEL), state);
428     ShowWindow(GetDlgItem(hDlg, IDC_EDIT_DEVICE), state);
429     ShowWindow(GetDlgItem(hDlg, IDC_STATIC_LABEL), state);
430     ShowWindow(GetDlgItem(hDlg, IDC_BUTTON_BROWSE_DEVICE), state);
431     SetWindowPos(GetDlgItem(hDlg, ID_BUTTON_OK),
432                  HWND_TOP,
433                  okpos.left, okpos.top - offset, okpos.right - okpos.left,
434                  okpos.bottom - okpos.top,
435                  0);
436
437     /* resize the parent window */
438     GetWindowRect(hDlg, &current_window);
439     SetWindowPos(hDlg, 
440                  HWND_TOP,
441                  current_window.left,
442                  current_window.top,
443                  windowpos.right - windowpos.left,
444                  windowpos.bottom - windowpos.top - offset,
445                  0);
446
447     /* update the button text based on the state */
448     SetWindowText(GetDlgItem(hDlg, IDC_BUTTON_SHOW_HIDE_ADVANCED),
449                   text);
450 }
451
452
453 void refreshDriveEditDialog(HWND dialog) {
454   char *path;
455   uint type;
456   char *label;
457   char *serial;
458   char *device;
459   int i, selection = -1;
460
461   updatingUI = TRUE;
462
463   WINE_TRACE("\n");
464   
465   /* Drive letters */
466   fill_drive_droplist( drive_available_mask( editDriveEntry->letter ), editDriveEntry->letter, dialog );
467
468   /* path */
469   path = editDriveEntry->unixpath;
470   if (path) {
471     WINE_TRACE("set path control text to '%s'\n", path);
472     SetWindowText(GetDlgItem(dialog, IDC_EDIT_PATH), path);
473   } else WINE_WARN("no Path field?\n");
474   
475   /* drive type */
476   type = editDriveEntry->type;
477   if (type) {
478     for(i = 0; i < sizeof(type_pairs)/sizeof(code_desc_pair); i++) {
479       SendDlgItemMessage(dialog, IDC_COMBO_TYPE, CB_ADDSTRING, 0,
480                          (LPARAM) type_pairs[i].sDesc);
481       if(type_pairs[i].sCode ==  type){
482           selection = i;
483       }
484     }
485   
486     if( selection == -1 ) selection = DRIVE_TYPE_DEFAULT;
487     SendDlgItemMessage(dialog, IDC_COMBO_TYPE, CB_SETCURSEL, selection, 0);
488   } else WINE_WARN("no Type field?\n");
489
490   
491   /* removeable media properties */
492   label = editDriveEntry->label;
493   if (label) {
494     SendDlgItemMessage(dialog, IDC_EDIT_LABEL, WM_SETTEXT, 0,(LPARAM)label);
495   } else WINE_WARN("no Label field?\n");
496
497   /* set serial edit text */
498   serial = editDriveEntry->serial;
499   if (serial) {
500     SendDlgItemMessage(dialog, IDC_EDIT_SERIAL, WM_SETTEXT, 0,(LPARAM)serial);
501   } else WINE_WARN("no Serial field?\n");
502
503   /* TODO: get the device here to put into the edit box */
504   device = "Not implemented yet";
505   if (device) {
506     SendDlgItemMessage(dialog, IDC_EDIT_DEVICE, WM_SETTEXT, 0,(LPARAM)device);
507   } else WINE_WARN("no Device field?\n");
508
509   selection = IDC_RADIO_ASSIGN;
510   if ((type == DRIVE_CDROM) || (type == DRIVE_REMOVABLE)) {
511 #if 0
512     if (device) {
513       selection = IDC_RADIO_AUTODETECT;
514       enable_labelserial_box(dialog, BOX_MODE_CD_AUTODETECT);
515     } else {
516 #endif
517       selection = IDC_RADIO_ASSIGN;
518       enable_labelserial_box(dialog, BOX_MODE_CD_ASSIGN);
519 #if 0
520     }
521 #endif
522   } else {
523     enable_labelserial_box(dialog, BOX_MODE_NORMAL);
524     selection = IDC_RADIO_ASSIGN;
525   }
526
527   CheckRadioButton( dialog, IDC_RADIO_AUTODETECT, IDC_RADIO_ASSIGN, selection );
528   
529   updatingUI = FALSE;
530   
531   return;
532 }
533
534 /* storing the drive propsheet HWND here is a bit ugly, but the simplest solution for now */
535 static HWND driveDlgHandle;
536
537 void onEditChanged(HWND hDlg, WORD controlID) {
538   WINE_TRACE("controlID=%d\n", controlID);
539   switch (controlID) {
540       case IDC_EDIT_LABEL: {
541         char *label = getDialogItemText(hDlg, controlID);
542         if(!label) label = strdup("");
543         setDriveLabel(editDriveEntry, label);
544         refreshDriveDlg(driveDlgHandle);
545         if (label) free(label);
546         break;
547       }
548       case IDC_EDIT_PATH: {
549           char *path = getDialogItemText(hDlg, controlID);
550           if (!path) path = strdup("fake_windows"); /* default to assuming fake_windows in the .wine directory */
551           WINE_TRACE("got path from control of '%s'\n", path);
552           setDrivePath(editDriveEntry, path);
553           if(path) free(path);
554           break;
555       }
556       case IDC_EDIT_SERIAL: {
557           char *serial = getDialogItemText(hDlg, controlID);
558           if(!serial) serial = strdup("");
559           setDriveSerial(editDriveEntry, serial);
560           if (serial) free (serial);
561           break;
562       }
563       case IDC_EDIT_DEVICE: {
564           char *device = getDialogItemText(hDlg,controlID);
565           /* TODO: handle device if/when it makes sense to do so.... */
566           if (device) free(device);
567           refreshDriveDlg(driveDlgHandle);
568           break;
569       }
570   }
571 }
572
573 /* edit a drive entry */
574 INT_PTR CALLBACK DriveEditDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
575 {
576   int selection;
577   static BOOL advanced = FALSE;
578
579   switch (uMsg)  {
580       case WM_CLOSE:
581             EndDialog(hDlg, wParam);
582             return TRUE;
583
584       case WM_INITDIALOG: {
585           enable_labelserial_box(hDlg, BOX_MODE_NORMAL);
586           advancedDriveEditDialog(hDlg, advanced);
587           editDriveEntry = (drive_entry_t*)lParam;
588           refreshDriveEditDialog(hDlg);
589       }
590
591     case WM_COMMAND:
592       switch (LOWORD(wParam)) {
593           case IDC_COMBO_TYPE:
594             if (HIWORD(wParam) != CBN_SELCHANGE) break;
595             selection = SendDlgItemMessage( hDlg, IDC_COMBO_TYPE, CB_GETCURSEL, 0, 0);
596             if( selection == 2 || selection == 3 ) { /* cdrom or floppy */
597               if (IsDlgButtonChecked(hDlg, IDC_RADIO_AUTODETECT))
598               enable_labelserial_box(hDlg, BOX_MODE_CD_AUTODETECT);
599               else
600               enable_labelserial_box(hDlg, BOX_MODE_CD_ASSIGN);
601             }
602             else {
603               enable_labelserial_box( hDlg, BOX_MODE_NORMAL );
604             }
605         editDriveEntry->type = type_pairs[selection].sCode;
606             break;
607
608           case IDC_COMBO_LETTER: {
609             int item = SendDlgItemMessage(hDlg, IDC_COMBO_LETTER, CB_GETCURSEL, 0, 0);
610             char newLetter[4];
611             SendDlgItemMessage(hDlg, IDC_COMBO_LETTER, CB_GETLBTEXT, item, (LPARAM) newLetter);
612
613             if (HIWORD(wParam) != CBN_SELCHANGE) break;
614             if (newLetter[0] == editDriveEntry->letter) break;
615
616             WINE_TRACE("changing drive letter to %c\n", newLetter[0]);
617             moveDrive(editDriveEntry, &drives[getDrive(newLetter[0])]);
618             editDriveEntry = &drives[getDrive(newLetter[0])];
619             refreshDriveDlg(driveDlgHandle);
620             break;
621           }
622
623           case IDC_BUTTON_BROWSE_PATH:
624             WRITEME(hDlg);
625             break;
626
627           case IDC_RADIO_AUTODETECT: {
628         /* TODO: */
629         WINE_FIXME("Implement autodetection\n");
630             enable_labelserial_box(hDlg, BOX_MODE_CD_AUTODETECT);
631         refreshDriveDlg(driveDlgHandle);
632             break;
633           }
634             
635           case IDC_RADIO_ASSIGN:
636       {
637         char *edit, *serial;
638         edit = getDialogItemText(hDlg, IDC_EDIT_LABEL);
639         if(!edit) edit = strdup("");
640         setDriveLabel(editDriveEntry, edit);
641         free(edit);
642
643         serial = getDialogItemText(hDlg, IDC_EDIT_SERIAL);
644         if(!serial) serial = strdup("");
645         setDriveSerial(editDriveEntry, serial);
646         free(serial);
647
648         /* TODO: we don't have a device at this point */
649 /*      setDriveValue(editWindowLetter, "Device", NULL); */
650             enable_labelserial_box(hDlg, BOX_MODE_CD_ASSIGN);
651             refreshDriveDlg(driveDlgHandle);
652             break;
653       }
654             
655       case IDC_BUTTON_SHOW_HIDE_ADVANCED:
656           advanced = (advanced == TRUE) ? FALSE : TRUE; /* toggle state */
657           advancedDriveEditDialog(hDlg, advanced);
658           break;
659
660           case ID_BUTTON_OK:      
661             EndDialog(hDlg, wParam);
662             return TRUE;
663
664       }
665       if (HIWORD(wParam) == EN_CHANGE) onEditChanged(hDlg, LOWORD(wParam));
666       break;
667   }
668   return FALSE;
669 }
670
671 void onAddDriveClicked(HWND hDlg) {
672   /* we should allocate a drive letter automatically. We also need some way to let the user choose the mapping point,
673      for now we will just force them to enter a path automatically, with / being the default. In future we should
674      be able to temporarily map / then invoke the directory chooser dialog. */
675   
676   char newLetter = 'C'; /* we skip A and B, they are historically floppy drives */
677   long mask = ~drive_available_mask(0); /* the mask is now which drives aren't available */
678   
679   while (mask & (1 << (newLetter - 'A'))) {
680     newLetter++;
681     if (newLetter > 'Z') {
682       MessageBox(NULL, "You cannot add any more drives.\n\nEach drive must have a letter, from A to Z, so you cannot have more than 26", "", MB_OK | MB_ICONEXCLAMATION);
683       return;
684     }
685   }
686   WINE_TRACE("allocating drive letter %c\n", newLetter);
687
688   if(newLetter == 'C') {
689       addDrive(newLetter, "fake_windows", "System Drive", "", DRIVE_FIXED);
690   } else {
691       addDrive(newLetter, "/", "", "", DRIVE_FIXED);
692   }
693
694   refreshDriveDlg(driveDlgHandle);
695
696   DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_DRIVE_EDIT), NULL, (DLGPROC) DriveEditDlgProc, (LPARAM) &(drives[getDrive(newLetter)]));
697 }
698
699 void onDriveInitDialog(void)
700 {
701   char *pDevices, *pDev;
702   int ret;
703   int i;
704   int retval;
705
706   WINE_TRACE("\n");
707
708   /* setup the drives array */
709   pDev = pDevices = malloc(512);
710   ret = GetLogicalDriveStrings(512, pDevices);
711
712   /* make all devices unused */
713   for(i = 0; i < 26; i++)
714   {
715       drives[i].letter = 'A' + i;
716       drives[i].in_use = FALSE;
717   }
718
719   i = 0;
720
721   while(ret)
722   {
723     CHAR volumeNameBuffer[512];
724     DWORD serialNumber;
725     CHAR serialNumberString[256];
726     DWORD maxComponentLength;
727     DWORD fileSystemFlags;
728     CHAR fileSystemName[128];
729     char rootpath[256];
730     char simplepath[3];
731     int pathlen;
732     char targetpath[256];
733
734     *pDevices = toupper(*pDevices);
735
736     WINE_TRACE("pDevices == '%s'\n", pDevices);
737
738     volumeNameBuffer[0] = 0;
739     
740     retval = GetVolumeInformation(pDevices,
741                          volumeNameBuffer,
742                          sizeof(volumeNameBuffer),
743                          &serialNumber,
744                          &maxComponentLength,
745                          &fileSystemFlags,
746                          fileSystemName,
747                          sizeof(fileSystemName));
748     if(!retval)
749     {
750         WINE_TRACE("GetVolumeInformation() for '%s' failed, setting serialNumber to 0\n", pDevices);
751         PRINTERROR();
752         serialNumber = 0;
753     }
754
755     WINE_TRACE("serialNumber: '0x%lX'\n", serialNumber);
756
757     /* build rootpath for GetDriveType() */
758     strncpy(rootpath, pDevices, sizeof(rootpath));
759     pathlen = strlen(rootpath);
760     /* ensure that we have a backslash on the root path */
761     if((rootpath[pathlen - 1] != '\\') &&
762        (pathlen < sizeof(rootpath)))
763      {
764          rootpath[pathlen] = '\\';
765          rootpath[pathlen + 1] = 0;
766      }
767
768     strncpy(simplepath, pDevices, 2); /* QueryDosDevice() requires no trailing backslash */
769     simplepath[2] = 0;
770     QueryDosDevice(simplepath, targetpath, sizeof(targetpath)); 
771     
772     snprintf(serialNumberString, sizeof(serialNumberString), "%lX", serialNumber);
773     WINE_TRACE("serialNumberString: '%s'\n", serialNumberString);
774     addDrive(*pDevices, targetpath, volumeNameBuffer, serialNumberString, GetDriveType(rootpath));
775
776     ret-=strlen(pDevices);
777     pDevices+=strlen(pDevices);
778
779     /* skip over any nulls */
780     while((*pDevices == 0) && (ret))
781     {
782         ret--;
783         pDevices++;
784     }
785
786     i++;
787   }
788
789   WINE_TRACE("found %d drives\n", i);
790
791   free(pDev);
792 }
793
794
795 void applyDriveChanges(void)
796 {
797     int i;
798     CHAR devicename[4];
799     CHAR targetpath[256];
800     BOOL foundDrive;
801     CHAR volumeNameBuffer[512];
802     DWORD serialNumber;
803     DWORD maxComponentLength;
804     DWORD fileSystemFlags;
805     CHAR fileSystemName[128];
806     int retval;
807     BOOL defineDevice;
808
809     WINE_TRACE("\n");
810
811     /* add each drive and remove as we go */
812     for(i = 0; i < 26; i++)
813     {
814         defineDevice = FALSE;
815         foundDrive = FALSE;
816         snprintf(devicename, sizeof(devicename), "%c:", 'A' + i); 
817
818         /* get a drive */
819         if(QueryDosDevice(devicename, targetpath, sizeof(targetpath)))
820         {
821             foundDrive = TRUE;
822         }
823
824         /* if we found a drive and have a drive then compare things */
825         if(foundDrive && drives[i].in_use)
826         {
827             char newSerialNumberText[256];
828
829             volumeNameBuffer[0] = 0;
830
831             WINE_TRACE("drives[i].letter: '%c'\n", drives[i].letter);
832
833             snprintf(devicename, sizeof(devicename), "%c:\\", 'A' + i);
834             retval = GetVolumeInformation(devicename,
835                          volumeNameBuffer,
836                          sizeof(volumeNameBuffer),
837                          &serialNumber,
838                          &maxComponentLength,
839                          &fileSystemFlags,
840                          fileSystemName,
841                          sizeof(fileSystemName));
842             if(!retval)
843             {
844                 WINE_TRACE("  GetVolumeInformation() for '%s' failed\n", devicename);
845                 WINE_TRACE("  Skipping this drive\n");
846                 PRINTERROR();
847                 continue; /* skip this drive */
848             }
849
850             snprintf(newSerialNumberText, sizeof(newSerialNumberText), "%lX", serialNumber);
851
852             WINE_TRACE("  current path:   '%s', new path:   '%s'\n",
853                        targetpath, drives[i].unixpath);
854             WINE_TRACE("  current label:  '%s', new label:  '%s'\n",
855                        volumeNameBuffer, drives[i].label);
856             WINE_TRACE("  current serial: '%s', new serial: '%s'\n",
857                        newSerialNumberText, drives[i].serial);
858
859             /* compare to what we have */
860             /* do we have the same targetpath? */
861             if(strcmp(drives[i].unixpath, targetpath) ||
862                strcmp(drives[i].label, volumeNameBuffer) ||
863                strcmp(drives[i].serial, newSerialNumberText))
864             {
865                 defineDevice = TRUE;
866                 WINE_TRACE("  making changes to drive '%s'\n", devicename);
867             } else
868             {
869                 WINE_TRACE("  no changes to drive '%s'\n", devicename);
870             }
871         } else if(foundDrive && !drives[i].in_use)
872         {
873             /* remove this drive */
874             if(!DefineDosDevice(DDD_REMOVE_DEFINITION, devicename, drives[i].unixpath))
875             {
876                 WINE_ERR("unable to remove devicename of '%s', targetpath of '%s'\n",
877                     devicename, drives[i].unixpath);
878                 PRINTERROR();
879             } else
880             {
881                 WINE_TRACE("removed devicename of '%s', targetpath of '%s'\n",
882                            devicename, drives[i].unixpath);
883             }
884         } else if(drives[i].in_use) /* foundDrive must be false from the above check */
885         {
886             defineDevice = TRUE;
887         }
888
889         /* adding and modifying are the same steps */
890         if(defineDevice)
891         {
892             char filename[256];
893             HANDLE hFile;
894
895             HKEY hKey;
896             char *typeText;
897             char driveValue[256];
898
899             /* define this drive */
900             /* DefineDosDevice() requires that NO trailing slash be present */
901             snprintf(devicename, sizeof(devicename), "%c:", 'A' + i); 
902             if(!DefineDosDevice(DDD_RAW_TARGET_PATH, devicename, drives[i].unixpath))
903             {
904                 WINE_ERR("  unable to define devicename of '%s', targetpath of '%s'\n",
905                     devicename, drives[i].unixpath);
906                 PRINTERROR();
907             } else
908             {
909                 WINE_TRACE("  added devicename of '%s', targetpath of '%s'\n",
910                            devicename, drives[i].unixpath);
911                 
912                 /* SetVolumeLabel() requires a trailing slash */
913                 snprintf(devicename, sizeof(devicename), "%c:\\", 'A' + i);
914                 if(!SetVolumeLabel(devicename, drives[i].label))
915                 {
916                     WINE_ERR("unable to set volume label for devicename of '%s', label of '%s'\n",
917                         devicename, drives[i].label);
918                     PRINTERROR();
919                 } else
920                 {
921                     WINE_TRACE("  set volume label for devicename of '%s', label of '%s'\n",
922                         devicename, drives[i].label);
923                 }
924             }
925
926             /* Set the drive type in the registry */
927             if(drives[i].type == DRIVE_FIXED)
928                 typeText = "hd";
929             else if(drives[i].type == DRIVE_REMOTE)
930                 typeText = "network";
931             else if(drives[i].type == DRIVE_REMOVABLE)
932                 typeText = "floppy";
933             else /* must be DRIVE_CDROM */
934                 typeText = "cdrom";
935             
936
937             snprintf(driveValue, sizeof(driveValue), "%c:", toupper(drives[i].letter));
938
939             retval = RegOpenKey(HKEY_LOCAL_MACHINE,
940                        "Software\\Wine\\Drives",
941                        &hKey);
942
943             if(retval != ERROR_SUCCESS)
944             {
945                 WINE_TRACE("  Unable to open '%s'\n", "Software\\Wine\\Drives");
946             } else
947             {
948                 retval = RegSetValueEx(
949                               hKey,
950                               driveValue,
951                               0,
952                               REG_SZ,
953                               typeText,
954                               strlen(typeText) + 1);
955                 if(retval != ERROR_SUCCESS)
956                 {
957                     WINE_TRACE("  Unable to set value of '%s' to '%s'\n",
958                                driveValue, typeText);
959                 } else
960                 {
961                     WINE_TRACE("  Finished setting value of '%s' to '%s'\n",
962                                driveValue, typeText);
963                     RegCloseKey(hKey);
964                 }
965             }
966
967             /* Set the drive serial number via a .windows-serial file in */
968             /* the targetpath directory */
969             snprintf(filename, sizeof(filename), "%c:\\.windows-serial", drives[i].letter);
970             WINE_TRACE("  Putting serial number of '%ld' into file '%s'\n",
971                        serialNumber, filename);
972             hFile = CreateFile(filename,
973                        GENERIC_WRITE,
974                        FILE_SHARE_READ,
975                        NULL,
976                        CREATE_ALWAYS,
977                        FILE_ATTRIBUTE_NORMAL,
978                        NULL);
979             if(hFile)
980             {
981                 WINE_TRACE("  writing serial number of '%s'\n", drives[i].serial);
982                 WriteFile(hFile,
983                           drives[i].serial,
984                           strlen(drives[i].serial),
985                           NULL,
986                           NULL); 
987                 WriteFile(hFile,
988                           "\n",
989                           strlen("\n"),
990                           NULL,
991                           NULL); 
992                 CloseHandle(hFile);
993             } else
994             {
995                 WINE_TRACE("  CreateFile() error with file '%s'\n", filename);
996             }
997         }
998
999         /* if this drive is in use we should free it up */
1000         if(drives[i].in_use)
1001         {
1002             freeDrive(&drives[i]); /* free up the string memory */
1003         }
1004     }
1005 }
1006
1007
1008 INT_PTR CALLBACK
1009 DriveDlgProc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
1010 {
1011   int nItem;
1012   drive_entry_t *pDrive;
1013
1014   switch (uMsg) {
1015       case WM_INITDIALOG:
1016           onDriveInitDialog();
1017           break;
1018       case WM_COMMAND:
1019         switch (LOWORD(wParam)) {
1020             case IDC_LIST_DRIVES:
1021               /* double click should open the edit window for the chosen drive */
1022               if (HIWORD(wParam) == LBN_DBLCLK)
1023                 SendMessageA(hDlg, WM_COMMAND, IDC_BUTTON_EDIT, 0);
1024
1025               if (HIWORD(wParam) == LBN_SELCHANGE) lastSel = SendDlgItemMessage(hDlg, IDC_LIST_DRIVES, LB_GETCURSEL, 0, 0);
1026               break;
1027
1028             case IDC_BUTTON_ADD:
1029               onAddDriveClicked(hDlg);
1030               break;
1031
1032             case IDC_BUTTON_REMOVE:
1033               if (HIWORD(wParam) != BN_CLICKED) break;
1034               nItem = SendDlgItemMessage(hDlg, IDC_LIST_DRIVES,  LB_GETCURSEL, 0, 0);
1035               pDrive = (drive_entry_t*)SendDlgItemMessage(hDlg, IDC_LIST_DRIVES, LB_GETITEMDATA, nItem, 0);
1036           freeDrive(pDrive);
1037               refreshDriveDlg(driveDlgHandle);
1038               break;
1039               
1040             case IDC_BUTTON_EDIT:
1041               if (HIWORD(wParam) != BN_CLICKED) break;
1042               nItem = SendMessage(GetDlgItem(hDlg, IDC_LIST_DRIVES),  LB_GETCURSEL, 0, 0);
1043               pDrive = (drive_entry_t*)SendMessage(GetDlgItem(hDlg, IDC_LIST_DRIVES), LB_GETITEMDATA, nItem, 0);
1044               DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_DRIVE_EDIT), NULL, (DLGPROC) DriveEditDlgProc, (LPARAM) pDrive);
1045               break;
1046
1047             case IDC_BUTTON_AUTODETECT:
1048               WRITEME(hDlg);
1049               break;
1050         }
1051         break;
1052         
1053       case WM_NOTIFY: switch(((LPNMHDR)lParam)->code) {
1054             case PSN_KILLACTIVE:
1055           WINE_TRACE("PSN_KILLACTIVE\n");
1056               SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
1057               break;
1058             case PSN_APPLY:
1059           applyDriveChanges();
1060               SetWindowLong(hDlg, DWL_MSGRESULT, PSNRET_NOERROR);
1061               break;
1062             case PSN_SETACTIVE:
1063               driveDlgHandle = hDlg;
1064               refreshDriveDlg (driveDlgHandle);
1065               break;
1066         }
1067         break;
1068   }
1069
1070   return FALSE;
1071 }