- rewrite the transaction system to be based on a settings overlay,
[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 (currentApp) {
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   } else {
223     WINE_TRACE("disabling controls\n");
224     disable(IDC_LIST_DRIVES);
225     disable(IDC_BUTTON_ADD);
226     disable(IDC_BUTTON_REMOVE);
227     disable(IDC_BUTTON_EDIT);
228     disable(IDC_BUTTON_AUTODETECT);    
229   }
230
231   
232   updatingUI = FALSE;
233   return driveCount;
234 }
235
236 /******************************************************************************/
237 /*  The Drive Editing Dialog                                                  */
238 /******************************************************************************/
239 #define DRIVE_MASK_BIT(B) 1<<(toupper(B)-'A')
240
241 typedef struct {
242   const uint sCode;
243   const char *sDesc;
244 } code_desc_pair;
245
246 static code_desc_pair type_pairs[] = {
247   {DRIVE_FIXED,      "Local hard disk"},
248   {DRIVE_REMOTE,     "Network share" },
249   {DRIVE_REMOVABLE,  "Floppy disk"},
250   {DRIVE_CDROM,      "CD-ROM"}
251 };
252 #define DRIVE_TYPE_DEFAULT 1
253
254
255 void fill_drive_droplist(long mask, char currentLetter, HWND hDlg)
256 {
257   int i;
258   int selection;
259   int count;
260   int next_letter;
261   char sName[4] = "A:";
262
263   for( i=0, count=0, selection=-1, next_letter=-1; i <= 'Z'-'A'; ++i ) {
264     if( mask & DRIVE_MASK_BIT('A'+i) ) {
265       int index;
266       
267       sName[0] = 'A' + i;
268       index = SendDlgItemMessage( hDlg, IDC_COMBO_LETTER, CB_ADDSTRING, 0, (LPARAM) sName );
269                           
270       if( toupper(currentLetter) == 'A' + i ) {
271           selection = count;
272       }
273       
274       if( i >= 2 && next_letter == -1){ /*default drive is first one of C-Z */
275           next_letter = count;
276       }
277       
278       count++;
279     }
280   }
281   
282   if( selection == -1 ) {
283     selection = next_letter;
284   }
285   
286   SendDlgItemMessage( hDlg, IDC_COMBO_LETTER, CB_SETCURSEL, selection, 0 );
287 }
288
289 #define BOX_MODE_CD_ASSIGN 1
290 #define BOX_MODE_CD_AUTODETECT 2
291 #define BOX_MODE_NONE 3
292 #define BOX_MODE_NORMAL 4
293 void enable_labelserial_box(HWND dialog, int mode)
294 {
295   WINE_TRACE("mode=%d\n", mode);
296   switch (mode) {
297       case BOX_MODE_CD_ASSIGN:
298 /*      enable(IDC_RADIO_AUTODETECT); */
299         enable(IDC_RADIO_ASSIGN);
300         disable(IDC_EDIT_DEVICE);
301         disable(IDC_BUTTON_BROWSE_DEVICE);
302         enable(IDC_EDIT_SERIAL);
303         enable(IDC_EDIT_LABEL);
304         enable(IDC_STATIC_SERIAL);
305         enable(IDC_STATIC_LABEL);
306         break;
307         
308       case BOX_MODE_CD_AUTODETECT:
309 /*      enable(IDC_RADIO_AUTODETECT); */
310         enable(IDC_RADIO_ASSIGN);
311         enable(IDC_EDIT_DEVICE);
312         enable(IDC_BUTTON_BROWSE_DEVICE);
313         disable(IDC_EDIT_SERIAL);
314         disable(IDC_EDIT_LABEL);
315         disable(IDC_STATIC_SERIAL);
316         disable(IDC_STATIC_LABEL);
317         break;
318
319       case BOX_MODE_NONE:
320         disable(IDC_RADIO_AUTODETECT);
321         disable(IDC_RADIO_ASSIGN);
322         disable(IDC_EDIT_DEVICE);
323         disable(IDC_BUTTON_BROWSE_DEVICE);
324         disable(IDC_EDIT_SERIAL);
325         disable(IDC_EDIT_LABEL);
326         disable(IDC_STATIC_SERIAL);
327         disable(IDC_STATIC_LABEL);
328         break;
329
330       case BOX_MODE_NORMAL:
331         disable(IDC_RADIO_AUTODETECT);
332         enable(IDC_RADIO_ASSIGN);
333         disable(IDC_EDIT_DEVICE);
334         disable(IDC_BUTTON_BROWSE_DEVICE);
335         enable(IDC_EDIT_SERIAL);
336         enable(IDC_EDIT_LABEL);
337         enable(IDC_STATIC_SERIAL);
338         enable(IDC_STATIC_LABEL);
339         break;  
340   }
341 }
342
343 /* This function produces a mask for each drive letter that isn't currently used. Each bit of the long result
344  * represents a letter, with A being the least significant bit, and Z being the most significant.
345  *
346  * To calculate this, we loop over each letter, and see if we can get a drive entry for it. If so, we
347  * set the appropriate bit. At the end, we flip each bit, to give the desired result.
348  *
349  * The letter parameter is always marked as being available. This is so the edit dialog can display the
350  * currently used drive letter alongside the available ones.
351  */
352 long drive_available_mask(char letter)
353 {
354   long result = 0;
355   int i;
356
357   WINE_TRACE("\n");
358
359   
360   for(i = 0; i < 26; i++)
361   {
362       if(!drives[i].in_use) continue;
363       result |= (1 << (toupper(drives[i].letter) - 'A'));
364   }
365
366   result = ~result;
367   if (letter) result |= DRIVE_MASK_BIT(letter);
368   
369   WINE_TRACE( "finished drive letter loop with %lx\n", result );
370   return result;
371 }
372
373 void advancedDriveEditDialog(HWND hDlg, BOOL showAdvanced)
374 {
375 #define ADVANCED_DELTA      120
376
377     static RECT okpos;
378     static BOOL got_initial_ok_position = FALSE;
379
380     static RECT windowpos; /* we only use the height of this rectangle */
381     static BOOL got_initial_window_position = FALSE;
382
383     static RECT current_window;
384
385     INT state;
386     INT offset;
387     char *text;
388
389     if(!got_initial_ok_position)
390     {
391         POINT pt;
392         GetWindowRect(GetDlgItem(hDlg, ID_BUTTON_OK), &okpos);
393         pt.x = okpos.left;
394         pt.y = okpos.top;
395         ScreenToClient(hDlg, &pt);
396         okpos.right+= (pt.x - okpos.left);
397         okpos.bottom+= (pt.y - okpos.top);
398         okpos.left = pt.x;
399         okpos.top = pt.y;
400         got_initial_ok_position = TRUE;
401     }
402
403     if(!got_initial_window_position)
404     {
405         GetWindowRect(hDlg, &windowpos);
406         got_initial_window_position = TRUE;
407     }
408           
409     if(showAdvanced)
410     {
411         state = SW_NORMAL;
412         offset = 0;
413         text = "Hide Advanced";
414     } else
415     {
416         state = SW_HIDE;
417         offset = ADVANCED_DELTA;
418         text = "Show Advanced";
419     }
420
421     ShowWindow(GetDlgItem(hDlg, IDC_STATIC_TYPE), state);
422     ShowWindow(GetDlgItem(hDlg, IDC_COMBO_TYPE), state);
423     ShowWindow(GetDlgItem(hDlg, IDC_BOX_LABELSERIAL), state);
424     ShowWindow(GetDlgItem(hDlg, IDC_RADIO_AUTODETECT), state);
425     ShowWindow(GetDlgItem(hDlg, IDC_RADIO_ASSIGN), state);
426     ShowWindow(GetDlgItem(hDlg, IDC_EDIT_LABEL), state);
427     ShowWindow(GetDlgItem(hDlg, IDC_EDIT_DEVICE), state);
428     ShowWindow(GetDlgItem(hDlg, IDC_STATIC_LABEL), state);
429     ShowWindow(GetDlgItem(hDlg, IDC_BUTTON_BROWSE_DEVICE), state);
430     SetWindowPos(GetDlgItem(hDlg, ID_BUTTON_OK),
431                  HWND_TOP,
432                  okpos.left, okpos.top - offset, okpos.right - okpos.left,
433                  okpos.bottom - okpos.top,
434                  0);
435
436     /* resize the parent window */
437     GetWindowRect(hDlg, &current_window);
438     SetWindowPos(hDlg, 
439                  HWND_TOP,
440                  current_window.left,
441                  current_window.top,
442                  windowpos.right - windowpos.left,
443                  windowpos.bottom - windowpos.top - offset,
444                  0);
445
446     /* update the button text based on the state */
447     SetWindowText(GetDlgItem(hDlg, IDC_BUTTON_SHOW_HIDE_ADVANCED),
448                   text);
449 }
450
451
452 void refreshDriveEditDialog(HWND dialog) {
453   char *path;
454   uint type;
455   char *label;
456   char *serial;
457   char *device;
458   unsigned int i;
459   int 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
1019       case WM_SHOWWINDOW:
1020           set_window_title(hDlg);
1021           break;
1022           
1023       case WM_COMMAND:
1024         switch (LOWORD(wParam)) {
1025             case IDC_LIST_DRIVES:
1026               /* double click should open the edit window for the chosen drive */
1027               if (HIWORD(wParam) == LBN_DBLCLK)
1028                 SendMessageA(hDlg, WM_COMMAND, IDC_BUTTON_EDIT, 0);
1029
1030               if (HIWORD(wParam) == LBN_SELCHANGE) lastSel = SendDlgItemMessage(hDlg, IDC_LIST_DRIVES, LB_GETCURSEL, 0, 0);
1031               break;
1032
1033             case IDC_BUTTON_ADD:
1034               onAddDriveClicked(hDlg);
1035               break;
1036
1037             case IDC_BUTTON_REMOVE:
1038               if (HIWORD(wParam) != BN_CLICKED) break;
1039               nItem = SendDlgItemMessage(hDlg, IDC_LIST_DRIVES,  LB_GETCURSEL, 0, 0);
1040               pDrive = (drive_entry_t*)SendDlgItemMessage(hDlg, IDC_LIST_DRIVES, LB_GETITEMDATA, nItem, 0);
1041           freeDrive(pDrive);
1042               refreshDriveDlg(driveDlgHandle);
1043               break;
1044               
1045             case IDC_BUTTON_EDIT:
1046               if (HIWORD(wParam) != BN_CLICKED) break;
1047               nItem = SendMessage(GetDlgItem(hDlg, IDC_LIST_DRIVES),  LB_GETCURSEL, 0, 0);
1048               pDrive = (drive_entry_t*)SendMessage(GetDlgItem(hDlg, IDC_LIST_DRIVES), LB_GETITEMDATA, nItem, 0);
1049               DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_DRIVE_EDIT), NULL, (DLGPROC) DriveEditDlgProc, (LPARAM) pDrive);
1050               break;
1051
1052             case IDC_BUTTON_AUTODETECT:
1053               WRITEME(hDlg);
1054               break;
1055         }
1056         break;
1057         
1058       case WM_NOTIFY: switch(((LPNMHDR)lParam)->code) {
1059             case PSN_KILLACTIVE:
1060           WINE_TRACE("PSN_KILLACTIVE\n");
1061               SetWindowLong(hDlg, DWL_MSGRESULT, FALSE);
1062               break;
1063             case PSN_APPLY:
1064           applyDriveChanges();
1065               SetWindowLong(hDlg, DWL_MSGRESULT, PSNRET_NOERROR);
1066               break;
1067             case PSN_SETACTIVE:
1068               driveDlgHandle = hDlg;
1069               refreshDriveDlg (driveDlgHandle);
1070               break;
1071         }
1072         break;
1073   }
1074
1075   return FALSE;
1076 }